diff options
Diffstat (limited to 'starmath/source')
60 files changed, 41007 insertions, 0 deletions
diff --git a/starmath/source/ElementsDockingWindow.cxx b/starmath/source/ElementsDockingWindow.cxx new file mode 100644 index 0000000000..b00e927172 --- /dev/null +++ b/starmath/source/ElementsDockingWindow.cxx @@ -0,0 +1,687 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <ElementsDockingWindow.hxx> + +#include <starmath.hrc> +#include <strings.hrc> +#include <smmod.hxx> +#include <cfgitem.hxx> +#include <parse.hxx> +#include <utility> +#include <view.hxx> +#include <visitors.hxx> +#include <document.hxx> +#include <strings.hxx> + +#include <sfx2/dispatch.hxx> +#include <sfx2/sfxmodelfactory.hxx> +#include <svl/stritem.hxx> +#include <vcl/event.hxx> +#include <vcl/settings.hxx> +#include <vcl/uitest/eventdescription.hxx> +#include <vcl/uitest/logger.hxx> +#include <vcl/virdev.hxx> + +#include <unordered_map> + +namespace +{ +// element, element help, element visual, element visual's translatable +typedef std::tuple<std::u16string_view, TranslateId, std::u16string_view, TranslateId> SmElementDescr; + +// SmParser 5 elements + +const SmElementDescr s_a5UnaryBinaryOperatorsList[] = +{ + {RID_PLUSX, RID_PLUSX_HELP, {}, {}}, + {RID_MINUSX, RID_MINUSX_HELP, {}, {}}, + {RID_PLUSMINUSX, RID_PLUSMINUSX_HELP, {}, {}}, + {RID_MINUSPLUSX, RID_MINUSPLUSX_HELP, {}, {}}, + {}, + {RID_XPLUSY, RID_XPLUSY_HELP, {}, {}}, + {RID_XMINUSY, RID_XMINUSY_HELP, {}, {}}, + {RID_XCDOTY, RID_XCDOTY_HELP, {}, {}}, + {RID_XTIMESY, RID_XTIMESY_HELP, {}, {}}, + {RID_XSYMTIMESY, RID_XSYMTIMESY_HELP, {}, {}}, + {RID_XOVERY, RID_XOVERY_HELP, {}, {}}, + {RID_FRACXY, RID_FRACXY_HELP, {}, {}}, + {RID_XDIVY, RID_XDIVY_HELP, {}, {}}, + {RID_XSYMDIVIDEY, RID_XSYMDIVIDEY_HELP, {}, {}}, + {RID_XOPLUSY, RID_XOPLUSY_HELP, {}, {}}, + {RID_XOMINUSY, RID_XOMINUSY_HELP, {}, {}}, + {RID_XODOTY, RID_XODOTY_HELP, {}, {}}, + {RID_XOTIMESY, RID_XOTIMESY_HELP, {}, {}}, + {RID_XODIVIDEY, RID_XODIVIDEY_HELP, {}, {}}, + {RID_XCIRCY, RID_XCIRCY_HELP, {}, {}}, + {RID_XWIDESLASHY, RID_XWIDESLASHY_HELP, {}, {}}, + {RID_XWIDEBSLASHY, RID_XWIDEBSLASHY_HELP, {}, {}}, + {}, + {RID_NEGX, RID_NEGX_HELP, {}, {}}, + {RID_XANDY, RID_XANDY_HELP, {}, {}}, + {RID_XORY, RID_XORY_HELP, {}, {}} +}; + +const SmElementDescr s_a5RelationsList[] = +{ + {RID_XEQY, RID_XEQY_HELP, {}, {}}, + {RID_XNEQY, RID_XNEQY_HELP, {}, {}}, + {RID_XLTY, RID_XLTY_HELP, {}, {}}, + {RID_XLEY, RID_XLEY_HELP, {}, {}}, + {RID_XLESLANTY, RID_XLESLANTY_HELP, {}, {}}, + {RID_XGTY, RID_XGTY_HELP, {}, {}}, + {RID_XGEY, RID_XGEY_HELP, {}, {}}, + {RID_XGESLANTY, RID_XGESLANTY_HELP, {}, {}}, + {RID_XLLY, RID_XLLY_HELP, {}, {}}, + {RID_XGGY, RID_XGGY_HELP, {}, {}}, + {}, + {RID_XAPPROXY, RID_XAPPROXY_HELP, {}, {}}, + {RID_XSIMY, RID_XSIMY_HELP, {}, {}}, + {RID_XSIMEQY, RID_XSIMEQY_HELP, {}, {}}, + {RID_XEQUIVY, RID_XEQUIVY_HELP, {}, {}}, + {RID_XPROPY, RID_XPROPY_HELP, {}, {}}, + {RID_XPARALLELY, RID_XPARALLELY_HELP, {}, {}}, + {RID_XORTHOY, RID_XORTHOY_HELP, {}, {}}, + {RID_XDIVIDESY, RID_XDIVIDESY_HELP, {}, {}}, + {RID_XNDIVIDESY, RID_XNDIVIDESY_HELP, {}, {}}, + {RID_XTOWARDY, RID_XTOWARDY_HELP, {}, {}}, + {RID_XTRANSLY, RID_XTRANSLY_HELP, {}, {}}, + {RID_XTRANSRY, RID_XTRANSRY_HELP, {}, {}}, + {RID_XDEFY, RID_XDEFY_HELP, {}, {}}, + {}, + {RID_DLARROW, RID_DLARROW_HELP, {}, {}}, + {RID_DLRARROW, RID_DLRARROW_HELP, {}, {}}, + {RID_DRARROW, RID_DRARROW_HELP, {}, {}}, + {}, + {RID_XPRECEDESY, RID_XPRECEDESY_HELP, {}, {}}, + {RID_XSUCCEEDSY, RID_XSUCCEEDSY_HELP, {}, {}}, + {RID_XPRECEDESEQUALY, RID_XPRECEDESEQUALY_HELP, {}, {}}, + {RID_XSUCCEEDSEQUALY, RID_XSUCCEEDSEQUALY_HELP, {}, {}}, + {RID_XPRECEDESEQUIVY, RID_XPRECEDESEQUIVY_HELP, {}, {}}, + {RID_XSUCCEEDSEQUIVY, RID_XSUCCEEDSEQUIVY_HELP, {}, {}}, + {RID_XNOTPRECEDESY, RID_XNOTPRECEDESY_HELP, {}, {}}, + {RID_XNOTSUCCEEDSY, RID_XNOTSUCCEEDSY_HELP, {}, {}}, +}; + +const SmElementDescr s_a5SetOperationsList[] = +{ + {RID_XINY, RID_XINY_HELP, {}, {}}, + {RID_XNOTINY, RID_XNOTINY_HELP, {}, {}}, + {RID_XOWNSY, RID_XOWNSY_HELP, {}, {}}, + {}, + {RID_XINTERSECTIONY, RID_XINTERSECTIONY_HELP, {}, {}}, + {RID_XUNIONY, RID_XUNIONY_HELP, {}, {}}, + {RID_XSETMINUSY, RID_XSETMINUSY_HELP, {}, {}}, + {RID_XSETQUOTIENTY, RID_XSETQUOTIENTY_HELP, {}, {}}, + {RID_XSUBSETY, RID_XSUBSETY_HELP, {}, {}}, + {RID_XSUBSETEQY, RID_XSUBSETEQY_HELP, {}, {}}, + {RID_XSUPSETY, RID_XSUPSETY_HELP, {}, {}}, + {RID_XSUPSETEQY, RID_XSUPSETEQY_HELP, {}, {}}, + {RID_XNSUBSETY, RID_XNSUBSETY_HELP, {}, {}}, + {RID_XNSUBSETEQY, RID_XNSUBSETEQY_HELP, {}, {}}, + {RID_XNSUPSETY, RID_XNSUPSETY_HELP, {}, {}}, + {RID_XNSUPSETEQY, RID_XNSUPSETEQY_HELP, {}, {}}, + {}, + {RID_EMPTYSET, RID_EMPTYSET_HELP, {}, {}}, + {RID_ALEPH, RID_ALEPH_HELP, {}, {}}, + {RID_SETN, RID_SETN_HELP, {}, {}}, + {RID_SETZ, RID_SETZ_HELP, {}, {}}, + {RID_SETQ, RID_SETQ_HELP, {}, {}}, + {RID_SETR, RID_SETR_HELP, {}, {}}, + {RID_SETC, RID_SETC_HELP, {}, {}} +}; + +const SmElementDescr s_a5FunctionsList[] = +{ + {RID_ABSX, RID_ABSX_HELP, {}, {}}, + {RID_FACTX, RID_FACTX_HELP, {}, {}}, + {RID_SQRTX, RID_SQRTX_HELP, {}, {}}, + {RID_NROOTXY, RID_NROOTXY_HELP, {}, {}}, + {RID_RSUPX, RID_RSUPX_HELP, {}, {}}, + {RID_EX, RID_EX_HELP, {}, {}}, + {RID_LNX, RID_LNX_HELP, {}, {}}, + {RID_EXPX, RID_EXPX_HELP, {}, {}}, + {RID_LOGX, RID_LOGX_HELP, {}, {}}, + {RID_ARALOGX, RID_SINX_HELP, {}, {}}, + {}, + {RID_SINX, RID_SINX_HELP, {}, {}}, + {RID_COSX, RID_COSX_HELP, {}, {}}, + {RID_TANX, RID_TANX_HELP, {}, {}}, + {RID_COTX, RID_COTX_HELP, {}, {}}, + {RID_SINHX, RID_SINHX_HELP, {}, {}}, + {RID_COSHX, RID_COSHX_HELP, {}, {}}, + {RID_TANHX, RID_TANHX_HELP, {}, {}}, + {RID_COTHX, RID_COTHX_HELP, {}, {}}, + {}, + {RID_ARASINX, RID_SINX_HELP, {}, {}}, + {RID_ARACOSX, RID_COSX_HELP, {}, {}}, + {RID_ARATANX, RID_TANX_HELP, {}, {}}, + {RID_ARACOTX, RID_COTX_HELP, {}, {}}, + {RID_ARASECX, RID_COTX_HELP, {}, {}}, + {RID_ARACSCX, RID_COTX_HELP, {}, {}}, + {RID_ARASINHX, RID_SINHX_HELP, {}, {}}, + {RID_ARACOSHX, RID_COSHX_HELP, {}, {}}, + {RID_ARATANHX, RID_TANHX_HELP, {}, {}}, + {RID_ARACOTHX, RID_COTHX_HELP, {}, {}}, + {RID_ARASECHX, RID_COTX_HELP, {}, {}}, + {RID_ARACSCHX, RID_COTX_HELP, {}, {}}, + {}, + {RID_ARASIN2X, RID_SINX_HELP, {}, {}}, + {RID_ARACOS2X, RID_COSX_HELP, {}, {}}, + {RID_ARATAN2X, RID_TANX_HELP, {}, {}}, + {RID_ARACOT2X, RID_COTX_HELP, {}, {}}, + {RID_ARASEC2X, RID_COTX_HELP, {}, {}}, + {RID_ARACSC2X, RID_COTX_HELP, {}, {}}, + {RID_ARASINH2X, RID_SINHX_HELP, {}, {}}, + {RID_ARACOSH2X, RID_COSHX_HELP, {}, {}}, + {RID_ARATANH2X, RID_TANHX_HELP, {}, {}}, + {RID_ARACOTH2X, RID_COTHX_HELP, {}, {}}, + {RID_ARASECH2X, RID_COTX_HELP, {}, {}}, + {RID_ARACSCH2X, RID_COTX_HELP, {}, {}}, + {}, + {RID_ARCSINX, RID_ARCSINX_HELP, {}, {}}, + {RID_ARCCOSX, RID_ARCCOSX_HELP, {}, {}}, + {RID_ARCTANX, RID_ARCTANX_HELP, {}, {}}, + {RID_ARCCOTX, RID_ARCCOTX_HELP, {}, {}}, + {RID_ARSINHX, RID_ARSINHX_HELP, {}, {}}, + {RID_ARCOSHX, RID_ARCOSHX_HELP, {}, {}}, + {RID_ARTANHX, RID_ARTANHX_HELP, {}, {}}, + {RID_ARCOTHX, RID_ARCOTHX_HELP, {}, {}}, + {}, + {RID_FUNCX, RID_FUNCX_HELP, {}, {}}, +}; + +const SmElementDescr s_a5OperatorsList[] = +{ + {RID_LIMX, RID_LIMX_HELP, {}, {}}, + {RID_LIM_FROMX, RID_LIM_FROMX_HELP, {}, {}}, + {RID_LIM_TOX, RID_LIM_TOX_HELP, {}, {}}, + {RID_LIM_FROMTOX, RID_LIM_FROMTOX_HELP, {}, {}}, + {}, + {RID_LIMINFX, RID_LIMINFX_HELP, {}, {}}, + {RID_LIMINF_FROMX, RID_LIMINF_FROMX_HELP, {}, {}}, + {RID_LIMINF_TOX, RID_LIMINF_TOX_HELP, {}, {}}, + {RID_LIMINF_FROMTOX, RID_LIMINF_FROMTOX_HELP, {}, {}}, + {}, + {RID_LIMSUPX, RID_LIMSUPX_HELP, {}, {}}, + {RID_LIMSUP_FROMX, RID_LIMSUP_FROMX_HELP, {}, {}}, + {RID_LIMSUP_TOX, RID_LIMSUP_TOX_HELP, {}, {}}, + {RID_LIMSUP_FROMTOX, RID_LIMSUP_FROMTOX_HELP, {}, {}}, + {}, + {RID_HADDX, RID_HADDX_HELP, {}, {}}, + {RID_HADD_FROMX, RID_HADD_FROMX_HELP, {}, {}}, + {RID_HADD_TOX, RID_HADD_TOX_HELP, {}, {}}, + {RID_HADD_FROMTOX, RID_HADD_FROMTOX_HELP, {}, {}}, + {}, + {RID_SUMX, RID_SUMX_HELP, {}, {}}, + {RID_SUM_FROMX, RID_SUM_FROMX_HELP, {}, {}}, + {RID_SUM_TOX, RID_SUM_TOX_HELP, {}, {}}, + {RID_SUM_FROMTOX, RID_SUM_FROMTOX_HELP, {}, {}}, + {}, + {RID_MAJX, RID_MAJX_HELP, {}, {}}, + {RID_MAJ_FROMX, RID_MAJ_FROMX_HELP, {}, {}}, + {RID_MAJ_TOX, RID_MAJ_TOX_HELP, {}, {}}, + {RID_MAJ_FROMTOX, RID_MAJ_FROMTOX_HELP, {}, {}}, + {}, + {RID_PRODX, RID_PRODX_HELP, {}, {}}, + {RID_PROD_FROMX, RID_PROD_FROMX_HELP, {}, {}}, + {RID_PROD_TOX, RID_PROD_TOX_HELP, {}, {}}, + {RID_PROD_FROMTOX, RID_PROD_FROMTOX_HELP, {}, {}}, + {}, + {RID_COPRODX, RID_COPRODX_HELP, {}, {}}, + {RID_COPROD_FROMX, RID_COPROD_FROMX_HELP, {}, {}}, + {RID_COPROD_TOX, RID_COPROD_TOX_HELP, {}, {}}, + {RID_COPROD_FROMTOX, RID_COPROD_FROMTOX_HELP, {}, {}}, + {}, + {RID_INTX, RID_INTX_HELP, {}, {}}, + {RID_INT_FROMX, RID_INT_FROMX_HELP, {}, {}}, + {RID_INT_TOX, RID_INT_TOX_HELP, {}, {}}, + {RID_INT_FROMTOX, RID_INT_FROMTOX_HELP, {}, {}}, + {}, + {RID_IINTX, RID_IINTX_HELP, {}, {}}, + {RID_IINT_FROMX, RID_IINT_FROMX_HELP, {}, {}}, + {RID_IINT_TOX, RID_IINT_TOX_HELP, {}, {}}, + {RID_IINT_FROMTOX, RID_IINT_FROMTOX_HELP, {}, {}}, + {}, + {RID_IIINTX, RID_IIINTX_HELP, {}, {}}, + {RID_IIINT_FROMX, RID_IIINT_FROMX_HELP, {}, {}}, + {RID_IIINT_TOX, RID_IIINT_TOX_HELP, {}, {}}, + {RID_IIINT_FROMTOX, RID_IIINT_FROMTOX_HELP, {}, {}}, + {}, + {RID_LINTX, RID_LINTX_HELP, {}, {}}, + {RID_LINT_FROMX, RID_LINT_FROMX_HELP, {}, {}}, + {RID_LINT_TOX, RID_LINT_TOX_HELP, {}, {}}, + {RID_LINT_FROMTOX, RID_LINT_FROMTOX_HELP, {}, {}}, + {}, + {RID_LLINTX, RID_LLINTX_HELP, {}, {}}, + {RID_LLINT_FROMX, RID_LLINT_FROMX_HELP, {}, {}}, + {RID_LLINT_TOX, RID_LLINT_TOX_HELP, {}, {}}, + {RID_LLINT_FROMTOX, RID_LLINT_FROMTOX_HELP, {}, {}}, + {}, + {RID_LLLINTX, RID_LLLINTX_HELP, {}, {}}, + {RID_LLLINT_FROMX, RID_LLLINT_FROMX_HELP, {}, {}}, + {RID_LLLINT_TOX, RID_LLLINT_TOX_HELP, {}, {}}, + {RID_LLLINT_FROMTOX, RID_LLLINT_FROMTOX_HELP, {}, {}}, + {}, + {RID_OPERX, RID_OPERX_HELP, u"oper \xE22B <?>", {}}, + {RID_OPER_FROMX, RID_OPER_FROMX_HELP, u"oper \xE22B from <?> <?>", {}}, + {RID_OPER_TOX, RID_OPER_TOX_HELP, u"oper \xE22B to <?> <?>", {}}, + {RID_OPER_FROMTOX, RID_OPER_FROMTOX_HELP, u"oper \xE22B from <?> to <?> <?>", {}}, +}; + +const SmElementDescr s_a5AttributesList[] = +{ + {RID_ACUTEX, RID_ACUTEX_HELP, {}, {}}, + {RID_GRAVEX, RID_GRAVEX_HELP, {}, {}}, + {RID_BREVEX, RID_BREVEX_HELP, {}, {}}, + {RID_CIRCLEX, RID_CIRCLEX_HELP, {}, {}}, + {RID_DOTX, RID_DOTX_HELP, {}, {}}, + {RID_DDOTX, RID_DDOTX_HELP, {}, {}}, + {RID_DDDOTX, RID_DDDOTX_HELP, {}, {}}, + {RID_BARX, RID_BARX_HELP, {}, {}}, + {RID_VECX, RID_VECX_HELP, {}, {}}, + {RID_HARPOONX, RID_HARPOONX_HELP, {}, {}}, + {RID_TILDEX, RID_TILDEX_HELP, {}, {}}, + {RID_HATX, RID_HATX_HELP, {}, {}}, + {RID_CHECKX, RID_CHECKX_HELP, {}, {}}, + {}, + {RID_WIDEVECX, RID_WIDEVECX_HELP, {}, {}}, + {RID_WIDEHARPOONX, RID_WIDEHARPOONX_HELP, {}, {}}, + {RID_WIDETILDEX, RID_WIDETILDEX_HELP, {}, {}}, + {RID_WIDEHATX, RID_WIDEHATX_HELP, {}, {}}, + {RID_OVERLINEX, RID_OVERLINEX_HELP, {}, {}}, + {RID_UNDERLINEX, RID_UNDERLINEX_HELP, {}, {}}, + {RID_OVERSTRIKEX, RID_OVERSTRIKEX_HELP, {}, {}}, + {}, + {RID_PHANTOMX, RID_PHANTOMX_HELP, u"\"$1\"", STR_HIDE}, + {RID_BOLDX, RID_BOLDX_HELP, u"bold B", {}}, + {RID_ITALX, RID_ITALX_HELP, u"ital I", {}}, + {RID_SIZEXY, RID_SIZEXY_HELP, u"\"$1\"", STR_SIZE}, + {RID_FONTXY, RID_FONTXY_HELP, u"\"$1\"", STR_FONT}, + {}, + {RID_COLORX_BLACK, RID_COLORX_BLACK_HELP, u"color black { \"$1\" }", STR_BLACK}, + {RID_COLORX_BLUE, RID_COLORX_BLUE_HELP, u"color blue { \"$1\" }", STR_BLUE}, + {RID_COLORX_GREEN, RID_COLORX_GREEN_HELP, u"color green { \"$1\" }", STR_GREEN}, + {RID_COLORX_RED, RID_COLORX_RED_HELP, u"color red { \"$1\" }", STR_RED}, + {RID_COLORX_AQUA, RID_COLORX_AQUA_HELP, u"color aqua { \"$1\" }", STR_AQUA}, + {RID_COLORX_FUCHSIA, RID_COLORX_FUCHSIA_HELP, u"color fuchsia { \"$1\" }", STR_FUCHSIA}, + {RID_COLORX_YELLOW, RID_COLORX_YELLOW_HELP, u"color yellow { \"$1\" }", STR_YELLOW}, + {RID_COLORX_GRAY, RID_COLORX_GRAY_HELP, u"color gray { \"$1\" }", STR_GRAY}, + {RID_COLORX_LIME, RID_COLORX_LIME_HELP, u"color lime { \"$1\" }", STR_LIME}, + {RID_COLORX_MAROON, RID_COLORX_MAROON_HELP, u"color maroon { \"$1\" }", STR_MAROON}, + {RID_COLORX_NAVY, RID_COLORX_NAVY_HELP, u"color navy { \"$1\" }", STR_NAVY}, + {RID_COLORX_OLIVE, RID_COLORX_OLIVE_HELP, u"color olive { \"$1\" }", STR_OLIVE}, + {RID_COLORX_PURPLE, RID_COLORX_PURPLE_HELP, u"color purple { \"$1\" }", STR_PURPLE}, + {RID_COLORX_SILVER, RID_COLORX_SILVER_HELP, u"color silver { \"$1\" }", STR_SILVER}, + {RID_COLORX_TEAL, RID_COLORX_TEAL_HELP, u"color teal { \"$1\" }", STR_TEAL}, + {RID_COLORX_RGB, RID_COLORX_RGB_HELP, u"color rgb 0 0 0 { \"$1\" }", STR_RGB}, + //{RID_COLORX_RGBA, RID_COLORX_RGBA_HELP, u"color rgba 0 0 0 0 { \"$1\" }", STR_RGBA}, + {RID_COLORX_HEX, RID_COLORX_HEX_HELP, u"color hex 000000 { \"$1\" }", STR_HEX}, + {}, + {RID_COLORX_CORAL, RID_COLORX_CORAL_HELP, u"color coral { \"$1\" }", STR_CORAL}, + {RID_COLORX_MIDNIGHT, RID_COLORX_MIDNIGHT_HELP, u"color midnightblue { \"$1\" }", STR_MIDNIGHT}, + {RID_COLORX_CRIMSON, RID_COLORX_CRIMSON_HELP, u"color crimson { \"$1\" }", STR_CRIMSON}, + {RID_COLORX_VIOLET, RID_COLORX_VIOLET_HELP, u"color violet { \"$1\" }", STR_VIOLET}, + {RID_COLORX_ORANGE, RID_COLORX_ORANGE_HELP, u"color orange { \"$1\" }", STR_ORANGE}, + {RID_COLORX_ORANGERED, RID_COLORX_ORANGERED_HELP, u"color orangered { \"$1\" }", STR_ORANGERED}, + {RID_COLORX_SEAGREEN, RID_COLORX_SEAGREEN_HELP, u"color seagreen { \"$1\" }", STR_SEAGREEN}, + {RID_COLORX_INDIGO, RID_COLORX_INDIGO_HELP, u"color indigo { \"$1\" }", STR_INDIGO}, + {RID_COLORX_HOTPINK, RID_COLORX_HOTPINK_HELP, u"color hotpink { \"$1\" }", STR_HOTPINK}, + {RID_COLORX_LAVENDER, RID_COLORX_LAVENDER_HELP, u"color lavender { \"$1\" }", STR_LAVENDER}, + {RID_COLORX_SNOW, RID_COLORX_SNOW_HELP, u"color snow { \"$1\" }", STR_SNOW}, +}; + +const SmElementDescr s_a5BracketsList[] = +{ + {RID_LRGROUPX, RID_LRGROUPX_HELP, {}, {}}, + {}, + {RID_LRPARENTX, RID_LRPARENTX_HELP, {}, {}}, + {RID_LRBRACKETX, RID_LRBRACKETX_HELP, {}, {}}, + {RID_LRDBRACKETX, RID_LRDBRACKETX_HELP, {}, {}}, + {RID_LRBRACEX, RID_LRBRACEX_HELP, {}, {}}, + {RID_LRANGLEX, RID_LRANGLEX_HELP, {}, {}}, + {RID_LMRANGLEXY, RID_LMRANGLEXY_HELP, {}, {}}, + {RID_LRCEILX, RID_LRCEILX_HELP, {}, {}}, + {RID_LRFLOORX, RID_LRFLOORX_HELP, {}, {}}, + {RID_LRLINEX, RID_LRLINEX_HELP, {}, {}}, + {RID_LRDLINEX, RID_LRDLINEX_HELP, {}, {}}, + {}, + {RID_SLRPARENTX, RID_SLRPARENTX_HELP, u"left ( binom{<?>}{<?>} right )", {}}, + {RID_SLRBRACKETX, RID_SLRBRACKETX_HELP, u"left [ binom{<?>}{<?>} right ]", {}}, + {RID_SLRDBRACKETX, RID_SLRDBRACKETX_HELP, u"left ldbracket binom{<?>}{<?>} right rdbracket", {}}, + {RID_SLRBRACEX, RID_SLRBRACEX_HELP, u"left lbrace binom{<?>}{<?>} right rbrace", {}}, + {RID_SLRANGLEX, RID_SLRANGLEX_HELP, u"left langle binom{<?>}{<?>} right rangle", {}}, + {RID_SLMRANGLEXY, RID_SLMRANGLEXY_HELP, u"left langle binom{<?>}{<?>} mline binom{<?>}{<?>} right rangle", {}}, + {RID_SLRCEILX, RID_SLRCEILX_HELP, u"left lceil binom{<?>}{<?>} right rceil", {}}, + {RID_SLRFLOORX, RID_SLRFLOORX_HELP, u"left lfloor binom{<?>}{<?>} right rfloor", {}}, + {RID_SLRLINEX, RID_SLRLINEX_HELP, u"left lline binom{<?>}{<?>} right rline", {}}, + {RID_SLRDLINEX, RID_SLRDLINEX_HELP, u"left ldline binom{<?>}{<?>} right rdline", {}}, + {}, + {RID_XOVERBRACEY, RID_XOVERBRACEY_HELP, u"{<?><?><?>} overbrace {<?>}", {}}, + {RID_XUNDERBRACEY, RID_XUNDERBRACEY_HELP, u"{<?><?><?>} underbrace {<?>} ", {}}, + {}, + {RID_EVALX, RID_EVALUATEX_HELP, {}, {}}, + {RID_EVAL_FROMX, RID_EVALUATE_FROMX_HELP, {}, {}}, + {RID_EVAL_TOX, RID_EVALUATE_TOX_HELP, {}, {}}, + {RID_EVAL_FROMTOX, RID_EVALUATE_FROMTOX_HELP, {}, {}}, +}; + +const SmElementDescr s_a5FormatsList[] = +{ + {RID_RSUPX, RID_RSUPX_HELP, {}, {}}, + {RID_RSUBX, RID_RSUBX_HELP, {}, {}}, + {RID_LSUPX, RID_LSUPX_HELP, {}, {}}, + {RID_LSUBX, RID_LSUBX_HELP, {}, {}}, + {RID_CSUPX, RID_CSUPX_HELP, {}, {}}, + {RID_CSUBX, RID_CSUBX_HELP, {}, {}}, + {}, + {RID_NEWLINE, RID_NEWLINE_HELP, u"\u21B5", {}}, + {RID_SBLANK, RID_SBLANK_HELP, u"\"`\"", {}}, + {RID_BLANK, RID_BLANK_HELP, u"\"~\"", {}}, + {RID_NOSPACE, RID_NOSPACE_HELP, {}, {}}, + {RID_ALIGNLX, RID_ALIGNLX_HELP, u"\"$1\"", STR_ALIGN_LEFT}, + {RID_ALIGNCX, RID_ALIGNCX_HELP, u"\"$1\"", STR_ALIGN_CENTER}, + {RID_ALIGNRX, RID_ALIGNRX_HELP, u"\"$1\"", STR_ALIGN_RIGHT}, + {}, + {RID_BINOMXY, RID_BINOMXY_HELP, {}, {}}, + {RID_STACK, RID_STACK_HELP, {}, {}}, + {RID_MATRIX, RID_MATRIX_HELP, {}, {}}, +}; + +const SmElementDescr s_a5OthersList[] = +{ + {RID_INFINITY, RID_INFINITY_HELP, {}, {}}, + {RID_PARTIAL, RID_PARTIAL_HELP, {}, {}}, + {RID_NABLA, RID_NABLA_HELP, {}, {}}, + {RID_EXISTS, RID_EXISTS_HELP, {}, {}}, + {RID_NOTEXISTS, RID_NOTEXISTS_HELP, {}, {}}, + {RID_FORALL, RID_FORALL_HELP, {}, {}}, + {RID_HBAR, RID_HBAR_HELP, {}, {}}, + {RID_LAMBDABAR, RID_LAMBDABAR_HELP, {}, {}}, + {RID_RE, RID_RE_HELP, {}, {}}, + {RID_IM, RID_IM_HELP, {}, {}}, + {RID_WP, RID_WP_HELP, {}, {}}, + {RID_LAPLACE, RID_LAPLACE_HELP, {}, {}}, + {RID_FOURIER, RID_FOURIER_HELP, {}, {}}, + {RID_BACKEPSILON, RID_BACKEPSILON_HELP, {}, {}}, + {}, + {RID_LEFTARROW, RID_LEFTARROW_HELP, {}, {}}, + {RID_RIGHTARROW, RID_RIGHTARROW_HELP, {}, {}}, + {RID_UPARROW, RID_UPARROW_HELP, {}, {}}, + {RID_DOWNARROW, RID_DOWNARROW_HELP, {}, {}}, + {}, + {RID_DOTSLOW, RID_DOTSLOW_HELP, {}, {}}, + {RID_DOTSAXIS, RID_DOTSAXIS_HELP, {}, {}}, + {RID_DOTSVERT, RID_DOTSVERT_HELP, {}, {}}, + {RID_DOTSUP, RID_DOTSUP_HELP, {}, {}}, + {RID_DOTSDOWN, RID_DOTSDOWN_HELP, {}, {}}, +}; + +const SmElementDescr s_a5ExamplesList[] = +{ + {u"{func e}^{i %pi} + 1 = 0", RID_EXAMPLE_EULER_IDENTITY_HELP, {}, {}}, + {u"C = %pi cdot d = 2 cdot %pi cdot r", RID_EXAMPLE_CIRCUMFERENCE_HELP, {}, {}}, + {u"c = sqrt{ a^2 + b^2 }", RID_EXAMPLE_PYTHAGOREAN_THEO_HELP, {}, {}}, + {u"vec F = m times vec a", RID_EXAMPLE_2NEWTON, {}, {}}, + {u"E = m c^2", RID_EXAMPLE_MASS_ENERGY_EQUIV_HELP, {}, {}}, + {u"G_{%mu %nu} + %LAMBDA g_{%mu %nu}= frac{8 %pi G}{c^4} T_{%mu %nu}", RID_EXAMPLE_GENERAL_RELATIVITY_HELP, {}, {}}, + {u"%DELTA t' = { %DELTA t } over sqrt{ 1 - v^2 over c^2 }", RID_EXAMPLE_SPECIAL_RELATIVITY_HELP, {}, {}}, + {u"d over dt left( {partial L}over{partial dot q} right) = {partial L}over{partial q}", RID_EXAMPLE_EULER_LAGRANGE_HELP, {}, {}}, + {u"int from a to b f'(x) dx = f(b) - f(a)", RID_EXAMPLE_FTC_HELP, {}, {}}, + {u"ldline %delta bold{r}(t) rdline approx e^{%lambda t} ldline %delta { bold{r} }_0 rdline", RID_EXAMPLE_CHAOS_HELP, {}, {}}, + {u"f(x) = sum from { n=0 } to { infinity } { {f^{(n)}(x_0) } over { fact{n} } (x-x_0)^n }", RID_EXAMPLE_A_TAYLOR_SERIES_HELP, {}, {}}, + {u"f(x) = {1} over { %sigma sqrt{2 %pi} } func e^-{ {(x-%mu)^2} over {2 %sigma^2} }", RID_EXAMPLE_GAUSS_DISTRIBUTION_HELP, {}, {}}, +}; + +const std::vector<TranslateId> s_a5Categories{ + RID_CATEGORY_UNARY_BINARY_OPERATORS, + RID_CATEGORY_RELATIONS, + RID_CATEGORY_SET_OPERATIONS, + RID_CATEGORY_FUNCTIONS, + RID_CATEGORY_OPERATORS, + RID_CATEGORY_ATTRIBUTES, + RID_CATEGORY_BRACKETS, + RID_CATEGORY_FORMATS, + RID_CATEGORY_OTHERS, + RID_CATEGORY_EXAMPLES, +}; + +template <size_t N> +constexpr std::pair<const SmElementDescr*, size_t> asPair(const SmElementDescr (&category)[N]) +{ + return { category, N }; +} + +const std::vector<std::pair<const SmElementDescr*, size_t>> s_a5CategoryDescriptions{ + { asPair(s_a5UnaryBinaryOperatorsList) }, + { asPair(s_a5RelationsList) }, + { asPair(s_a5SetOperationsList) }, + { asPair(s_a5FunctionsList) }, + { asPair(s_a5OperatorsList) }, + { asPair(s_a5AttributesList) }, + { asPair(s_a5BracketsList) }, + { asPair(s_a5FormatsList) }, + { asPair(s_a5OthersList) }, + { asPair(s_a5ExamplesList) }, +}; + +} // namespace + +// static +const std::vector<TranslateId>& SmElementsControl::categories() +{ + return s_a5Categories; +} + +struct ElementData +{ + OUString maElementSource; + OUString maHelpText; + ElementData(const OUString& aElementSource, const OUString& aHelpText) + : maElementSource(aElementSource) + , maHelpText(aHelpText) + { + } +}; + +SmElementsControl::SmElementsControl(std::unique_ptr<weld::IconView> pIconView) + : mpDocShell(new SmDocShell(SfxModelFlags::EMBEDDED_OBJECT)) + , mnCurrentSetIndex(-1) + , m_nSmSyntaxVersion(SM_MOD()->GetConfig()->GetDefaultSmSyntaxVersion()) + , mpIconView(std::move(pIconView)) +{ + maParser.reset(starmathdatabase::GetVersionSmParser(m_nSmSyntaxVersion)); + maParser->SetImportSymbolNames(true); + + mpIconView->connect_query_tooltip(LINK(this, SmElementsControl, QueryTooltipHandler)); + mpIconView->connect_item_activated(LINK(this, SmElementsControl, ElementActivatedHandler)); +} + +SmElementsControl::~SmElementsControl() +{ + mpDocShell->DoClose(); +} + +Color SmElementsControl::GetTextColor() +{ + const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); + return rStyleSettings.GetFieldTextColor(); +} + +Color SmElementsControl::GetControlBackground() +{ + const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); + return rStyleSettings.GetFieldColor(); +} + +namespace +{ + // SmTmpDevice::GetTextColor assumes a fg/bg of svtools::FONTCOLOR/svtools::DOCCOLOR + // here replace COL_AUTO with the desired fg color, alternatively could add something + // to SmTmpDevice to override its defaults + class AutoColorVisitor : public SmDefaultingVisitor + { + private: + Color m_aAutoColor; + public: + AutoColorVisitor(SmNode* pNode, Color aAutoColor) + : m_aAutoColor(aAutoColor) + { + DefaultVisit(pNode); + } + virtual void DefaultVisit(SmNode* pNode) override + { + if (pNode->GetFont().GetColor() == COL_AUTO) + pNode->GetFont().SetColor(m_aAutoColor); + size_t nNodes = pNode->GetNumSubNodes(); + for (size_t i = 0; i < nNodes; ++i) + { + SmNode* pChild = pNode->GetSubNode(i); + if (!pChild) + continue; + DefaultVisit(pChild); + } + } + }; +} + +void SmElementsControl::addElement(const OUString& aElementVisual, const OUString& aElementSource, const OUString& aHelpText) +{ + std::unique_ptr<SmNode> pNode = maParser->ParseExpression(aElementVisual); + ScopedVclPtr<VirtualDevice> pDevice(mpIconView->create_virtual_device()); + pDevice->SetMapMode(MapMode(SmMapUnit())); + pDevice->SetDrawMode(DrawModeFlags::Default); + pDevice->SetLayoutMode(vcl::text::ComplexTextLayoutFlags::Default); + pDevice->SetDigitLanguage(LANGUAGE_ENGLISH); + pDevice->EnableRTL(false); + + pDevice->SetBackground(GetControlBackground()); + pDevice->SetTextColor(GetTextColor()); + + pNode->Prepare(maFormat, *mpDocShell, 0); + pNode->SetSize(Fraction(10,8)); + pNode->Arrange(*pDevice, maFormat); + + AutoColorVisitor(pNode.get(), GetTextColor()); + + Size aSize = pDevice->LogicToPixel(Size(pNode->GetWidth(), pNode->GetHeight())); + aSize.extendBy(10, 0); // Add 5 pixels from both sides to accommodate extending parts of italics + pDevice->SetOutputSizePixel(aSize); + SmDrawingVisitor(*pDevice, pDevice->PixelToLogic(Point(5, 0)), pNode.get(), maFormat); + + maItemDatas.push_back(std::make_unique<ElementData>(aElementSource, aHelpText)); + const OUString aId(weld::toId(maItemDatas.back().get())); + mpIconView->insert(-1, nullptr, &aId, pDevice, nullptr); + if (mpIconView->get_item_width() < aSize.Width()) + mpIconView->set_item_width(aSize.Width()); +} + +OUString SmElementsControl::GetElementSource(const OUString& itemId) +{ + return weld::fromId<ElementData*>(itemId)->maElementSource; +} + +OUString SmElementsControl::GetElementHelpText(const OUString& itemId) +{ + return weld::fromId<ElementData*>(itemId)->maHelpText; +} + +void SmElementsControl::setElementSetIndex(int nSetIndex) +{ + if (mnCurrentSetIndex == nSetIndex) + return; + mnCurrentSetIndex = nSetIndex; + build(); +} + +void SmElementsControl::addElements(int nCategory) +{ + mpIconView->freeze(); + mpIconView->clear(); + mpIconView->set_item_width(0); + maItemDatas.clear(); + + assert(nCategory >= 0 && o3tl::make_unsigned(nCategory) < s_a5CategoryDescriptions.size()); + + const auto& [aElementsArray, aElementsArraySize] = s_a5CategoryDescriptions[nCategory]; + + for (size_t i = 0; i < aElementsArraySize; i++) + { + const auto& [element, elementHelp, elementVisual, visualTranslatable] = aElementsArray[i]; + if (element.empty()) + { + mpIconView->append_separator({}); + } + else + { + OUString aElement(element); + OUString aVisual(elementVisual.empty() ? aElement : OUString(elementVisual)); + if (visualTranslatable) + aVisual = aVisual.replaceFirst("$1", SmResId(visualTranslatable)); + OUString aHelp(elementHelp ? SmResId(elementHelp) : OUString()); + addElement(aVisual, aElement, aHelp); + } + } + + mpIconView->set_size_request(0, 0); + mpIconView->thaw(); +} + +void SmElementsControl::build() +{ + switch(m_nSmSyntaxVersion) + { + case 5: + addElements(mnCurrentSetIndex); + break; + case 6: + default: + throw std::range_error("parser version limit"); + } +} + +void SmElementsControl::setSmSyntaxVersion(sal_Int16 nSmSyntaxVersion) +{ + if( m_nSmSyntaxVersion != nSmSyntaxVersion ) + { + m_nSmSyntaxVersion = nSmSyntaxVersion; + maParser.reset(starmathdatabase::GetVersionSmParser(nSmSyntaxVersion)); + maParser->SetImportSymbolNames(true); + // Be careful, we need the parser in order to build !!! + build(); + } +} + +IMPL_LINK(SmElementsControl, QueryTooltipHandler, const weld::TreeIter&, iter, OUString) +{ + if (const OUString id = mpIconView->get_id(iter); !id.isEmpty()) + return GetElementHelpText(id); + return {}; +} + +IMPL_LINK_NOARG(SmElementsControl, ElementActivatedHandler, weld::IconView&, bool) +{ + if (const OUString id = mpIconView->get_selected_id(); !id.isEmpty()) + maSelectHdlLink.Call(GetElementSource(id)); + + mpIconView->unselect_all(); + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/SmElementsPanel.cxx b/starmath/source/SmElementsPanel.cxx new file mode 100644 index 0000000000..3abc025555 --- /dev/null +++ b/starmath/source/SmElementsPanel.cxx @@ -0,0 +1,102 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <comphelper/lok.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/lokcomponenthelpers.hxx> +#include <svl/stritem.hxx> +#include <svl/itemset.hxx> + +#include "SmElementsPanel.hxx" +#include <starmath.hrc> +#include <smmod.hxx> +#include <strings.hrc> +#include <view.hxx> + +namespace sm::sidebar +{ +// static +std::unique_ptr<PanelLayout> SmElementsPanel::Create(weld::Widget& rParent, + const SfxBindings& rBindings) +{ + return std::make_unique<SmElementsPanel>(rParent, rBindings); +} + +SmElementsPanel::SmElementsPanel(weld::Widget& rParent, const SfxBindings& rBindings) + : PanelLayout(&rParent, "MathElementsPanel", "modules/smath/ui/sidebarelements_math.ui") + , mrBindings(rBindings) + , mxCategoryList(m_xBuilder->weld_combo_box("categorylist")) + , mxElementsControl(std::make_unique<SmElementsControl>(m_xBuilder->weld_icon_view("elements"))) +{ + for (const auto& rCategoryId : SmElementsControl::categories()) + mxCategoryList->append_text(SmResId(rCategoryId)); + + mxCategoryList->set_size_request(-1, -1); + + mxCategoryList->connect_changed(LINK(this, SmElementsPanel, CategorySelectedHandle)); + mxCategoryList->set_active(0); + + mxElementsControl->setElementSetIndex(0); + mxElementsControl->SetSelectHdl(LINK(this, SmElementsPanel, ElementClickHandler)); +} + +SmElementsPanel::~SmElementsPanel() +{ + mxElementsControl.reset(); + mxCategoryList.reset(); +} + +IMPL_LINK(SmElementsPanel, CategorySelectedHandle, weld::ComboBox&, rList, void) +{ + const int nActive = rList.get_active(); + if (nActive == -1) + return; + mxElementsControl->setElementSetIndex(nActive); + if (SmViewShell* pViewSh = GetView()) + mxElementsControl->setSmSyntaxVersion(pViewSh->GetDoc()->GetSmSyntaxVersion()); +} + +IMPL_LINK(SmElementsPanel, ElementClickHandler, OUString, ElementSource, void) +{ + if (SmViewShell* pViewSh = GetView()) + { + SfxStringItem aInsertCommand(SID_INSERTCOMMANDTEXT, ElementSource); + pViewSh->GetViewFrame().GetDispatcher()->ExecuteList( + SID_INSERTCOMMANDTEXT, SfxCallMode::RECORD, { &aInsertCommand }); + } +} + +SmViewShell* SmElementsPanel::GetView() const +{ + SfxViewShell* pView = mrBindings.GetDispatcher()->GetFrame()->GetViewShell(); + SmViewShell* pSmViewShell = dynamic_cast<SmViewShell*>(pView); + if (!pSmViewShell && comphelper::LibreOfficeKit::isActive()) + { + auto* pWindow = static_cast<SmGraphicWindow*>(LokStarMathHelper(pView).GetGraphicWindow()); + if (pWindow) + pSmViewShell = &pWindow->GetGraphicWidget().GetView(); + } + return pSmViewShell; +} + +} // end of namespace sm::sidebar + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/starmath/source/SmElementsPanel.hxx b/starmath/source/SmElementsPanel.hxx new file mode 100644 index 0000000000..d7e4609a42 --- /dev/null +++ b/starmath/source/SmElementsPanel.hxx @@ -0,0 +1,56 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <sal/config.h> + +#include <sfx2/bindings.hxx> +#include <sfx2/sidebar/PanelLayout.hxx> +#include <vcl/customweld.hxx> +#include <vcl/EnumContext.hxx> + +#include <ElementsDockingWindow.hxx> + +#include <memory> + +namespace sm::sidebar +{ +class SmElementsPanel : public PanelLayout +{ +public: + static std::unique_ptr<PanelLayout> Create(weld::Widget& rParent, const SfxBindings& rBindings); + SmElementsPanel(weld::Widget& rParent, const SfxBindings& rBindings); + ~SmElementsPanel(); + +private: + DECL_LINK(CategorySelectedHandle, weld::ComboBox&, void); + DECL_LINK(ElementClickHandler, OUString, void); + + SmViewShell* GetView() const; + + const SfxBindings& mrBindings; + + std::unique_ptr<weld::ComboBox> mxCategoryList; + std::unique_ptr<SmElementsControl> mxElementsControl; +}; + +} // end of namespace sm::sidebar + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/starmath/source/SmPanelFactory.cxx b/starmath/source/SmPanelFactory.cxx new file mode 100644 index 0000000000..df35dcadff --- /dev/null +++ b/starmath/source/SmPanelFactory.cxx @@ -0,0 +1,136 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp> +#include <com/sun/star/ui/XUIElementFactory.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> + +#include <cppuhelper/exc_hlp.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <comphelper/namedvaluecollection.hxx> +#include <sfx2/sidebar/SidebarPanelBase.hxx> +#include <vcl/weldutils.hxx> + +#include "SmElementsPanel.hxx" +#include "SmPropertiesPanel.hxx" + +namespace +{ +typedef comphelper::WeakComponentImplHelper<css::ui::XUIElementFactory, css::lang::XServiceInfo> + PanelFactoryInterfaceBase; + +class SmPanelFactory final : public PanelFactoryInterfaceBase +{ +public: + SmPanelFactory() = default; + + SmPanelFactory(const SmPanelFactory&) = delete; + const SmPanelFactory& operator=(const SmPanelFactory&) = delete; + + // XUIElementFactory + css::uno::Reference<css::ui::XUIElement> SAL_CALL + createUIElement(const OUString& ResourceURL, + const css::uno::Sequence<css::beans::PropertyValue>& Arguments) override; + + // XServiceInfo + OUString SAL_CALL getImplementationName() override; + sal_Bool SAL_CALL supportsService(OUString const& ServiceName) override; + css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override; +}; + +css::uno::Reference<css::ui::XUIElement> SAL_CALL SmPanelFactory::createUIElement( + const OUString& ResourceURL, const css::uno::Sequence<css::beans::PropertyValue>& Arguments) +{ + try + { + const comphelper::NamedValueCollection aArguments(Arguments); + auto xFrame(aArguments.getOrDefault("Frame", css::uno::Reference<css::frame::XFrame>())); + auto xParentWindow( + aArguments.getOrDefault("ParentWindow", css::uno::Reference<css::awt::XWindow>())); + const sal_uInt64 nBindingsValue(aArguments.getOrDefault("SfxBindings", sal_uInt64(0))); + SfxBindings* pBindings = reinterpret_cast<SfxBindings*>(nBindingsValue); + + weld::Widget* pParent(nullptr); + if (auto pTunnel = dynamic_cast<weld::TransportAsXWindow*>(xParentWindow.get())) + pParent = pTunnel->getWidget(); + + if (!pParent) + throw css::uno::RuntimeException("SmPanelFactory::createUIElement: no ParentWindow"); + if (!xFrame) + throw css::uno::RuntimeException("SmPanelFactory::createUIElement: no Frame"); + if (!pBindings) + throw css::uno::RuntimeException("SmPanelFactory::createUIElement: no SfxBindings"); + + std::unique_ptr<PanelLayout> pPanel; + css::ui::LayoutSize aLayoutSize{ -1, -1, -1 }; + if (ResourceURL.endsWith("/MathPropertiesPanel")) + { + pPanel = sm::sidebar::SmPropertiesPanel::Create(*pParent, xFrame); + } + else if (ResourceURL.endsWith("/MathElementsPanel")) + { + pPanel = sm::sidebar::SmElementsPanel::Create(*pParent, *pBindings); + aLayoutSize = { 300, -1, -1 }; + } + + if (pPanel) + return sfx2::sidebar::SidebarPanelBase::Create(ResourceURL, xFrame, std::move(pPanel), + aLayoutSize); + } + catch (const css::uno::RuntimeException&) + { + throw; + } + catch (const css::uno::Exception&) + { + css::uno::Any anyEx = cppu::getCaughtException(); + throw css::lang::WrappedTargetRuntimeException("SmPanelFactory::createUIElement exception", + nullptr, anyEx); + } + + return {}; +} + +OUString SmPanelFactory::getImplementationName() +{ + return "org.libreoffice.comp.Math.sidebar.SmPanelFactory"; +} + +sal_Bool SmPanelFactory::supportsService(OUString const& ServiceName) +{ + return cppu::supportsService(this, ServiceName); +} + +css::uno::Sequence<OUString> SmPanelFactory::getSupportedServiceNames() +{ + return { "com.sun.star.ui.UIElementFactory" }; +} + +} // end of unnamed namespace + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +org_libreoffice_comp_Math_sidebar_SmPanelFactory(css::uno::XComponentContext*, + css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new SmPanelFactory); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/starmath/source/SmPropertiesPanel.cxx b/starmath/source/SmPropertiesPanel.cxx new file mode 100644 index 0000000000..48f2c6897c --- /dev/null +++ b/starmath/source/SmPropertiesPanel.cxx @@ -0,0 +1,92 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/frame/theUICommandDescription.hpp> +#include <com/sun/star/uno/Sequence.hxx> + +#include <comphelper/dispatchcommand.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/sequenceashashmap.hxx> + +#include "SmPropertiesPanel.hxx" + +namespace sm::sidebar +{ +// static +std::unique_ptr<PanelLayout> +SmPropertiesPanel::Create(weld::Widget& rParent, + const css::uno::Reference<css::frame::XFrame>& xFrame) +{ + return std::make_unique<SmPropertiesPanel>(rParent, xFrame); +} + +SmPropertiesPanel::SmPropertiesPanel(weld::Widget& rParent, + const css::uno::Reference<css::frame::XFrame>& xFrame) + : PanelLayout(&rParent, "MathPropertiesPanel", "modules/smath/ui/sidebarproperties_math.ui") + , mxFrame(xFrame) + , mpFormatFontsButton(m_xBuilder->weld_button("btnFormatFonts")) + , mpFormatFontSizeButton(m_xBuilder->weld_button("btnFormatFontSize")) + , mpFormatSpacingButton(m_xBuilder->weld_button("btnFormatSpacing")) + , mpFormatAlignmentButton(m_xBuilder->weld_button("btnFormatAlignment")) + , maButtonCommands{ { mpFormatFontsButton.get(), ".uno:ChangeFont" }, + { mpFormatFontSizeButton.get(), ".uno:ChangeFontSize" }, + { mpFormatSpacingButton.get(), ".uno:ChangeDistance" }, + { mpFormatAlignmentButton.get(), ".uno:ChangeAlignment" } } +{ + // Set localized labels to the buttons + auto xConfs + = css::frame::theUICommandDescription::get(comphelper::getProcessComponentContext()); + if (css::uno::Reference<css::container::XNameAccess> xConf{ + xConfs->getByName("com.sun.star.formula.FormulaProperties"), css::uno::UNO_QUERY }) + { + for (const auto & [ button, command ] : maButtonCommands) + { + comphelper::SequenceAsHashMap props(xConf->getByName(command)); + button->set_label(props.getUnpackedValueOrDefault("Name", button->get_label())); + } + } + + mpFormatFontsButton->connect_clicked(LINK(this, SmPropertiesPanel, ButtonClickHandler)); + mpFormatFontSizeButton->connect_clicked(LINK(this, SmPropertiesPanel, ButtonClickHandler)); + mpFormatSpacingButton->connect_clicked(LINK(this, SmPropertiesPanel, ButtonClickHandler)); + mpFormatAlignmentButton->connect_clicked(LINK(this, SmPropertiesPanel, ButtonClickHandler)); +} + +SmPropertiesPanel::~SmPropertiesPanel() +{ + maButtonCommands.clear(); + + mpFormatFontsButton.reset(); + mpFormatFontSizeButton.reset(); + mpFormatSpacingButton.reset(); + mpFormatAlignmentButton.reset(); +} + +IMPL_LINK(SmPropertiesPanel, ButtonClickHandler, weld::Button&, rButton, void) +{ + if (OUString command = maButtonCommands[&rButton]; !command.isEmpty()) + comphelper::dispatchCommand(command, mxFrame, {}); +} + +} // end of namespace sm::sidebar + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/starmath/source/SmPropertiesPanel.hxx b/starmath/source/SmPropertiesPanel.hxx new file mode 100644 index 0000000000..f19316e3fa --- /dev/null +++ b/starmath/source/SmPropertiesPanel.hxx @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <sal/config.h> + +#include <sfx2/sidebar/PanelLayout.hxx> + +#include <map> +#include <memory> + +namespace sm::sidebar +{ +class SmPropertiesPanel : public PanelLayout +{ +public: + static std::unique_ptr<PanelLayout> + Create(weld::Widget& rParent, const css::uno::Reference<css::frame::XFrame>& xFrame); + SmPropertiesPanel(weld::Widget& rParent, const css::uno::Reference<css::frame::XFrame>& xFrame); + ~SmPropertiesPanel(); + +private: + DECL_LINK(ButtonClickHandler, weld::Button&, void); + + css::uno::Reference<css::frame::XFrame> mxFrame; + + std::unique_ptr<weld::Button> mpFormatFontsButton; + std::unique_ptr<weld::Button> mpFormatFontSizeButton; + std::unique_ptr<weld::Button> mpFormatSpacingButton; + std::unique_ptr<weld::Button> mpFormatAlignmentButton; + + std::map<weld::Button*, OUString> maButtonCommands; +}; + +} // end of namespace sm::sidebar + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/starmath/source/accessibility.cxx b/starmath/source/accessibility.cxx new file mode 100644 index 0000000000..055cd0f66e --- /dev/null +++ b/starmath/source/accessibility.cxx @@ -0,0 +1,745 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <com/sun/star/accessibility/AccessibleRole.hpp> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/accessibility/AccessibleTextType.hpp> +#include <com/sun/star/accessibility/AccessibleEventObject.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <unotools/accessiblerelationsethelper.hxx> + +#include <com/sun/star/datatransfer/clipboard/XClipboard.hpp> +#include <com/sun/star/datatransfer/clipboard/XFlushableClipboard.hpp> +#include <comphelper/accessibleeventnotifier.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <osl/diagnose.h> +#include <comphelper/diagnose_ex.hxx> +#include <vcl/kernarray.hxx> +#include <vcl/svapp.hxx> +#include <vcl/unohelp2.hxx> +#include <vcl/settings.hxx> + +#include <tools/gen.hxx> + +#include <editeng/editobj.hxx> + + +#include "accessibility.hxx" +#include <document.hxx> +#include <view.hxx> +#include <strings.hrc> +#include <smmod.hxx> + +using namespace com::sun::star; +using namespace com::sun::star::lang; +using namespace com::sun::star::uno; +using namespace com::sun::star::accessibility; + +SmGraphicAccessible::SmGraphicAccessible(SmGraphicWidget *pGraphicWin) : + aAccName (SmResId(RID_DOCUMENTSTR)), + nClientId (0), + pWin (pGraphicWin) +{ + OSL_ENSURE( pWin, "SmGraphicAccessible: window missing" ); +} + +SmGraphicAccessible::~SmGraphicAccessible() +{ +} + +SmDocShell * SmGraphicAccessible::GetDoc_Impl() +{ + SmViewShell *pView = pWin ? &pWin->GetView() : nullptr; + return pView ? pView->GetDoc() : nullptr; +} + +OUString SmGraphicAccessible::GetAccessibleText_Impl() +{ + OUString aTxt; + SmDocShell *pDoc = GetDoc_Impl(); + if (pDoc) + aTxt = pDoc->GetAccessibleText(); + return aTxt; +} + +void SmGraphicAccessible::ClearWin() +{ + pWin = nullptr; // implicitly results in AccessibleStateType::DEFUNC set + + if ( nClientId ) + { + comphelper::AccessibleEventNotifier::revokeClientNotifyDisposing( std::exchange(nClientId, 0), *this ); + } +} + +void SmGraphicAccessible::LaunchEvent( + const sal_Int16 nAccessibleEventId, + const uno::Any &rOldVal, + const uno::Any &rNewVal) +{ + AccessibleEventObject aEvt; + aEvt.Source = static_cast<XAccessible *>(this); + aEvt.EventId = nAccessibleEventId; + aEvt.OldValue = rOldVal; + aEvt.NewValue = rNewVal ; + + // pass event on to event-listener's + if (nClientId) + comphelper::AccessibleEventNotifier::addEvent( nClientId, aEvt ); +} + +uno::Reference< XAccessibleContext > SAL_CALL SmGraphicAccessible::getAccessibleContext() +{ + return this; +} + +sal_Bool SAL_CALL SmGraphicAccessible::containsPoint( const awt::Point& aPoint ) +{ + //! the arguments coordinates are relative to the current window ! + //! Thus the top-left point is (0, 0) + + SolarMutexGuard aGuard; + if (!pWin) + throw RuntimeException(); + + Size aSz( pWin->GetOutputSizePixel() ); + return aPoint.X >= 0 && aPoint.Y >= 0 && + aPoint.X < aSz.Width() && aPoint.Y < aSz.Height(); +} + +uno::Reference< XAccessible > SAL_CALL SmGraphicAccessible::getAccessibleAtPoint( + const awt::Point& aPoint ) +{ + SolarMutexGuard aGuard; + XAccessible *pRes = nullptr; + if (containsPoint( aPoint )) + pRes = this; + return pRes; +} + +awt::Rectangle SAL_CALL SmGraphicAccessible::getBounds() +{ + SolarMutexGuard aGuard; + if (!pWin) + throw RuntimeException(); + + const Point aOutPos; + const Size aOutSize(pWin->GetOutputSizePixel()); + css::awt::Rectangle aRet; + + aRet.X = aOutPos.X(); + aRet.Y = aOutPos.Y(); + aRet.Width = aOutSize.Width(); + aRet.Height = aOutSize.Height(); + + return aRet; +} + +awt::Point SAL_CALL SmGraphicAccessible::getLocation() +{ + SolarMutexGuard aGuard; + if (!pWin) + throw RuntimeException(); + + const css::awt::Rectangle aRect(getBounds()); + css::awt::Point aRet; + + aRet.X = aRect.X; + aRet.Y = aRect.Y; + + return aRet; +} + +awt::Point SAL_CALL SmGraphicAccessible::getLocationOnScreen() +{ + SolarMutexGuard aGuard; + if (!pWin) + throw RuntimeException(); + + css::awt::Point aScreenLoc(0, 0); + + css::uno::Reference<css::accessibility::XAccessible> xParent(getAccessibleParent()); + if (xParent) + { + css::uno::Reference<css::accessibility::XAccessibleContext> xParentContext( + xParent->getAccessibleContext()); + css::uno::Reference<css::accessibility::XAccessibleComponent> xParentComponent( + xParentContext, css::uno::UNO_QUERY); + OSL_ENSURE(xParentComponent.is(), + "WeldEditAccessible::getLocationOnScreen: no parent component!"); + if (xParentComponent.is()) + { + css::awt::Point aParentScreenLoc(xParentComponent->getLocationOnScreen()); + css::awt::Point aOwnRelativeLoc(getLocation()); + aScreenLoc.X = aParentScreenLoc.X + aOwnRelativeLoc.X; + aScreenLoc.Y = aParentScreenLoc.Y + aOwnRelativeLoc.Y; + } + } + + return aScreenLoc; +} + +awt::Size SAL_CALL SmGraphicAccessible::getSize() +{ + SolarMutexGuard aGuard; + if (!pWin) + throw RuntimeException(); + Size aSz(pWin->GetOutputSizePixel()); + return css::awt::Size(aSz.Width(), aSz.Height()); +} + +void SAL_CALL SmGraphicAccessible::grabFocus() +{ + SolarMutexGuard aGuard; + if (!pWin) + throw RuntimeException(); + + pWin->GrabFocus(); +} + +sal_Int32 SAL_CALL SmGraphicAccessible::getForeground() +{ + SolarMutexGuard aGuard; + if (!pWin) + throw RuntimeException(); + + weld::DrawingArea* pDrawingArea = pWin->GetDrawingArea(); + OutputDevice& rDevice = pDrawingArea->get_ref_device(); + + return static_cast<sal_Int32>(rDevice.GetTextColor()); +} + +sal_Int32 SAL_CALL SmGraphicAccessible::getBackground() +{ + SolarMutexGuard aGuard; + if (!pWin) + throw RuntimeException(); + + weld::DrawingArea* pDrawingArea = pWin->GetDrawingArea(); + OutputDevice& rDevice = pDrawingArea->get_ref_device(); + + Wallpaper aWall(rDevice.GetBackground()); + Color nCol; + if (aWall.IsBitmap() || aWall.IsGradient()) + nCol = Application::GetSettings().GetStyleSettings().GetWindowColor(); + else + nCol = aWall.GetColor(); + return static_cast<sal_Int32>(nCol); +} + +sal_Int64 SAL_CALL SmGraphicAccessible::getAccessibleChildCount() +{ + return 0; +} + +Reference< XAccessible > SAL_CALL SmGraphicAccessible::getAccessibleChild( + sal_Int64 /*i*/ ) +{ + throw IndexOutOfBoundsException(); // there is no child... +} + +Reference< XAccessible > SAL_CALL SmGraphicAccessible::getAccessibleParent() +{ + SolarMutexGuard aGuard; + if (!pWin) + throw RuntimeException(); + + return pWin->GetDrawingArea()->get_accessible_parent(); +} + +sal_Int64 SAL_CALL SmGraphicAccessible::getAccessibleIndexInParent() +{ + SolarMutexGuard aGuard; + + // -1 for child not found/no parent (according to specification) + sal_Int64 nRet = -1; + + css::uno::Reference<css::accessibility::XAccessible> xParent(getAccessibleParent()); + if (!xParent) + return nRet; + + try + { + css::uno::Reference<css::accessibility::XAccessibleContext> xParentContext( + xParent->getAccessibleContext()); + + // iterate over parent's children and search for this object + if (xParentContext.is()) + { + sal_Int64 nChildCount = xParentContext->getAccessibleChildCount(); + for (sal_Int64 nChild = 0; (nChild < nChildCount) && (-1 == nRet); ++nChild) + { + css::uno::Reference<css::accessibility::XAccessible> xChild( + xParentContext->getAccessibleChild(nChild)); + if (xChild.get() == this) + nRet = nChild; + } + } + } + catch (const css::uno::Exception&) + { + TOOLS_WARN_EXCEPTION("svx", "WeldEditAccessible::getAccessibleIndexInParent"); + } + + return nRet; +} + +sal_Int16 SAL_CALL SmGraphicAccessible::getAccessibleRole() +{ + return AccessibleRole::DOCUMENT; +} + +OUString SAL_CALL SmGraphicAccessible::getAccessibleDescription() +{ + SolarMutexGuard aGuard; + SmDocShell *pDoc = GetDoc_Impl(); + return pDoc ? pDoc->GetText() : OUString(); +} + +OUString SAL_CALL SmGraphicAccessible::getAccessibleName() +{ + SolarMutexGuard aGuard; + return aAccName; +} + +Reference< XAccessibleRelationSet > SAL_CALL SmGraphicAccessible::getAccessibleRelationSet() +{ + return new utl::AccessibleRelationSetHelper(); // empty relation set +} + +sal_Int64 SAL_CALL SmGraphicAccessible::getAccessibleStateSet() +{ + SolarMutexGuard aGuard; + sal_Int64 nStateSet = 0; + + if (!pWin) + nStateSet |= AccessibleStateType::DEFUNC; + else + { + nStateSet |= AccessibleStateType::ENABLED; + nStateSet |= AccessibleStateType::FOCUSABLE; + if (pWin->HasFocus()) + nStateSet |= AccessibleStateType::FOCUSED; + if (pWin->IsActive()) + nStateSet |= AccessibleStateType::ACTIVE; + if (pWin->IsVisible()) + nStateSet |= AccessibleStateType::SHOWING; + if (pWin->IsReallyVisible()) + nStateSet |= AccessibleStateType::VISIBLE; + weld::DrawingArea* pDrawingArea = pWin->GetDrawingArea(); + OutputDevice& rDevice = pDrawingArea->get_ref_device(); + if (COL_TRANSPARENT != rDevice.GetBackground().GetColor()) + nStateSet |= AccessibleStateType::OPAQUE; + } + + return nStateSet; +} + +Locale SAL_CALL SmGraphicAccessible::getLocale() +{ + SolarMutexGuard aGuard; + // should be the document language... + // We use the language of the localized symbol names here. + return Application::GetSettings().GetUILanguageTag().getLocale(); +} + + +void SAL_CALL SmGraphicAccessible::addAccessibleEventListener( + const Reference< XAccessibleEventListener >& xListener ) +{ + if (xListener.is()) + { + SolarMutexGuard aGuard; + if (pWin) + { + if (!nClientId) + nClientId = comphelper::AccessibleEventNotifier::registerClient( ); + comphelper::AccessibleEventNotifier::addEventListener( nClientId, xListener ); + } + } +} + +void SAL_CALL SmGraphicAccessible::removeAccessibleEventListener( + const Reference< XAccessibleEventListener >& xListener ) +{ + if (!(xListener.is() && nClientId)) + return; + + SolarMutexGuard aGuard; + sal_Int32 nListenerCount = comphelper::AccessibleEventNotifier::removeEventListener( nClientId, xListener ); + if ( !nListenerCount ) + { + // no listeners anymore + // -> revoke ourself. This may lead to the notifier thread dying (if we were the last client), + // and at least to us not firing any events anymore, in case somebody calls + // NotifyAccessibleEvent, again + comphelper::AccessibleEventNotifier::revokeClient( std::exchange(nClientId, 0) ); + } +} + +sal_Int32 SAL_CALL SmGraphicAccessible::getCaretPosition() +{ + return 0; +} + +sal_Bool SAL_CALL SmGraphicAccessible::setCaretPosition( sal_Int32 nIndex ) +{ + SolarMutexGuard aGuard; + OUString aTxt( GetAccessibleText_Impl() ); + if (nIndex >= aTxt.getLength()) + throw IndexOutOfBoundsException(); + return false; +} + +sal_Unicode SAL_CALL SmGraphicAccessible::getCharacter( sal_Int32 nIndex ) +{ + SolarMutexGuard aGuard; + OUString aTxt( GetAccessibleText_Impl() ); + if (nIndex >= aTxt.getLength()) + throw IndexOutOfBoundsException(); + return aTxt[nIndex]; +} + +Sequence< beans::PropertyValue > SAL_CALL SmGraphicAccessible::getCharacterAttributes( + sal_Int32 nIndex, + const uno::Sequence< OUString > & /*rRequestedAttributes*/ ) +{ + SolarMutexGuard aGuard; + sal_Int32 nLen = GetAccessibleText_Impl().getLength(); + if (0 > nIndex || nIndex >= nLen) + throw IndexOutOfBoundsException(); + return Sequence< beans::PropertyValue >(); +} + +awt::Rectangle SAL_CALL SmGraphicAccessible::getCharacterBounds( sal_Int32 nIndex ) +{ + SolarMutexGuard aGuard; + + awt::Rectangle aRes; + + if (!pWin) + throw RuntimeException(); + + // get accessible text + SmDocShell* pDoc = pWin->GetView().GetDoc(); + if (!pDoc) + throw RuntimeException(); + OUString aTxt( GetAccessibleText_Impl() ); + if (0 > nIndex || nIndex > aTxt.getLength()) // aTxt.getLength() is valid + throw IndexOutOfBoundsException(); + + // find a reasonable rectangle for position aTxt.getLength(). + bool bWasBehindText = (nIndex == aTxt.getLength()); + if (bWasBehindText && nIndex) + --nIndex; + + const SmNode *pTree = pDoc->GetFormulaTree(); + const SmNode *pNode = pTree->FindNodeWithAccessibleIndex( nIndex ); + //! pNode may be 0 if the index belongs to a char that was inserted + //! only for the accessible text! + if (pNode) + { + sal_Int32 nAccIndex = pNode->GetAccessibleIndex(); + OSL_ENSURE( nAccIndex >= 0, "invalid accessible index" ); + OSL_ENSURE( nIndex >= nAccIndex, "index out of range" ); + + OUStringBuffer aBuf; + pNode->GetAccessibleText(aBuf); + OUString aNodeText = aBuf.makeStringAndClear(); + sal_Int32 nNodeIndex = nIndex - nAccIndex; + if (0 <= nNodeIndex && nNodeIndex < aNodeText.getLength()) + { + // get appropriate rectangle + Point aOffset(pNode->GetTopLeft() - pTree->GetTopLeft()); + Point aTLPos (pWin->GetFormulaDrawPos() + aOffset); + Size aSize (pNode->GetSize()); + + weld::DrawingArea* pDrawingArea = pWin->GetDrawingArea(); + OutputDevice& rDevice = pDrawingArea->get_ref_device(); + + KernArray aXAry; + rDevice.SetFont( pNode->GetFont() ); + rDevice.GetTextArray( aNodeText, &aXAry, 0, aNodeText.getLength() ); + aTLPos.AdjustX(nNodeIndex > 0 ? aXAry[nNodeIndex - 1] : 0 ); + aSize.setWidth( nNodeIndex > 0 ? aXAry[nNodeIndex] - aXAry[nNodeIndex - 1] : aXAry[nNodeIndex] ); + + aTLPos = rDevice.LogicToPixel( aTLPos ); + aSize = rDevice.LogicToPixel( aSize ); + aRes.X = aTLPos.X(); + aRes.Y = aTLPos.Y(); + aRes.Width = aSize.Width(); + aRes.Height = aSize.Height(); + } + } + + // take rectangle from last character and move it to the right + if (bWasBehindText) + aRes.X += aRes.Width; + + return aRes; +} + +sal_Int32 SAL_CALL SmGraphicAccessible::getCharacterCount() +{ + SolarMutexGuard aGuard; + return GetAccessibleText_Impl().getLength(); +} + +sal_Int32 SAL_CALL SmGraphicAccessible::getIndexAtPoint( const awt::Point& aPoint ) +{ + SolarMutexGuard aGuard; + + sal_Int32 nRes = -1; + if (pWin) + { + const SmNode *pTree = pWin->GetView().GetDoc()->GetFormulaTree(); + // can be NULL! e.g. if one clicks within the window already during loading of the + // document (before the parser even started) + if (!pTree) + return nRes; + + weld::DrawingArea* pDrawingArea = pWin->GetDrawingArea(); + OutputDevice& rDevice = pDrawingArea->get_ref_device(); + + // get position relative to formula draw position + Point aPos( aPoint.X, aPoint.Y ); + aPos = rDevice.PixelToLogic( aPos ); + aPos -= pWin->GetFormulaDrawPos(); + + // if it was inside the formula then get the appropriate node + const SmNode *pNode = nullptr; + if (pTree->OrientedDist(aPos) <= 0) + pNode = pTree->FindRectClosestTo(aPos); + + if (pNode) + { + // get appropriate rectangle + Point aOffset( pNode->GetTopLeft() - pTree->GetTopLeft() ); + Point aTLPos ( aOffset ); + Size aSize( pNode->GetSize() ); + + tools::Rectangle aRect( aTLPos, aSize ); + if (aRect.Contains( aPos )) + { + OSL_ENSURE( pNode->IsVisible(), "node is not a leaf" ); + OUStringBuffer aBuf; + pNode->GetAccessibleText(aBuf); + OUString aTxt = aBuf.makeStringAndClear(); + OSL_ENSURE( !aTxt.isEmpty(), "no accessible text available" ); + + tools::Long nNodeX = pNode->GetLeft(); + + KernArray aXAry; + rDevice.SetFont( pNode->GetFont() ); + rDevice.GetTextArray( aTxt, &aXAry, 0, aTxt.getLength() ); + for (sal_Int32 i = 0; i < aTxt.getLength() && nRes == -1; ++i) + { + if (aXAry[i] + nNodeX > aPos.X()) + nRes = i; + } + OSL_ENSURE( nRes >= 0 && nRes < aTxt.getLength(), "index out of range" ); + OSL_ENSURE( pNode->GetAccessibleIndex() >= 0, + "invalid accessible index" ); + + nRes = pNode->GetAccessibleIndex() + nRes; + } + } + } + return nRes; +} + +OUString SAL_CALL SmGraphicAccessible::getSelectedText() +{ + return OUString(); +} + +sal_Int32 SAL_CALL SmGraphicAccessible::getSelectionStart() +{ + return -1; +} + +sal_Int32 SAL_CALL SmGraphicAccessible::getSelectionEnd() +{ + return -1; +} + +sal_Bool SAL_CALL SmGraphicAccessible::setSelection( + sal_Int32 nStartIndex, + sal_Int32 nEndIndex ) +{ + SolarMutexGuard aGuard; + sal_Int32 nLen = GetAccessibleText_Impl().getLength(); + if (0 > nStartIndex || nStartIndex >= nLen || + 0 > nEndIndex || nEndIndex >= nLen) + throw IndexOutOfBoundsException(); + return false; +} + +OUString SAL_CALL SmGraphicAccessible::getText() +{ + SolarMutexGuard aGuard; + return GetAccessibleText_Impl(); +} + +OUString SAL_CALL SmGraphicAccessible::getTextRange( + sal_Int32 nStartIndex, + sal_Int32 nEndIndex ) +{ + //!! nEndIndex may be the string length per definition of the interface !! + //!! text should be copied exclusive that end index though. And arguments + //!! may be switched. + + SolarMutexGuard aGuard; + OUString aTxt( GetAccessibleText_Impl() ); + sal_Int32 nStart = std::min(nStartIndex, nEndIndex); + sal_Int32 nEnd = std::max(nStartIndex, nEndIndex); + if ((nStart > aTxt.getLength()) || + (nEnd > aTxt.getLength())) + throw IndexOutOfBoundsException(); + return aTxt.copy( nStart, nEnd - nStart ); +} + +css::accessibility::TextSegment SAL_CALL SmGraphicAccessible::getTextAtIndex( sal_Int32 nIndex, sal_Int16 aTextType ) +{ + SolarMutexGuard aGuard; + OUString aTxt( GetAccessibleText_Impl() ); + //!! nIndex is allowed to be the string length + if (nIndex > aTxt.getLength()) + throw IndexOutOfBoundsException(); + + css::accessibility::TextSegment aResult; + aResult.SegmentStart = -1; + aResult.SegmentEnd = -1; + if ( (AccessibleTextType::CHARACTER == aTextType) && (nIndex < aTxt.getLength()) ) + { + auto nIndexEnd = nIndex; + aTxt.iterateCodePoints(&nIndexEnd); + + aResult.SegmentText = aTxt.copy(nIndex, nIndexEnd - nIndex); + aResult.SegmentStart = nIndex; + aResult.SegmentEnd = nIndexEnd; + } + return aResult; +} + +css::accessibility::TextSegment SAL_CALL SmGraphicAccessible::getTextBeforeIndex( sal_Int32 nIndex, sal_Int16 aTextType ) +{ + SolarMutexGuard aGuard; + OUString aTxt( GetAccessibleText_Impl() ); + //!! nIndex is allowed to be the string length + if (nIndex > aTxt.getLength()) + throw IndexOutOfBoundsException(); + + css::accessibility::TextSegment aResult; + aResult.SegmentStart = -1; + aResult.SegmentEnd = -1; + + if ( (AccessibleTextType::CHARACTER == aTextType) && nIndex > 0 ) + { + aTxt.iterateCodePoints(&nIndex, -1); + auto nIndexEnd = nIndex; + aTxt.iterateCodePoints(&nIndexEnd); + aResult.SegmentText = aTxt.copy(nIndex, nIndexEnd - nIndex); + aResult.SegmentStart = nIndex; + aResult.SegmentEnd = nIndexEnd; + } + return aResult; +} + +css::accessibility::TextSegment SAL_CALL SmGraphicAccessible::getTextBehindIndex( sal_Int32 nIndex, sal_Int16 aTextType ) +{ + SolarMutexGuard aGuard; + OUString aTxt( GetAccessibleText_Impl() ); + //!! nIndex is allowed to be the string length + if (nIndex > aTxt.getLength()) + throw IndexOutOfBoundsException(); + + css::accessibility::TextSegment aResult; + aResult.SegmentStart = -1; + aResult.SegmentEnd = -1; + + if ( (AccessibleTextType::CHARACTER == aTextType) && (nIndex + 1 < aTxt.getLength()) ) + { + aTxt.iterateCodePoints(&nIndex); + auto nIndexEnd = nIndex; + aTxt.iterateCodePoints(&nIndexEnd); + aResult.SegmentText = aTxt.copy(nIndex, nIndexEnd - nIndex); + aResult.SegmentStart = nIndex; + aResult.SegmentEnd = nIndexEnd; + } + return aResult; +} + +sal_Bool SAL_CALL SmGraphicAccessible::copyText( + sal_Int32 nStartIndex, + sal_Int32 nEndIndex ) +{ + SolarMutexGuard aGuard; + bool bReturn = false; + + if (!pWin) + throw RuntimeException(); + + Reference< datatransfer::clipboard::XClipboard > xClipboard = pWin->GetClipboard(); + if ( xClipboard.is() ) + { + OUString sText( getTextRange(nStartIndex, nEndIndex) ); + + rtl::Reference<vcl::unohelper::TextDataObject> pDataObj = new vcl::unohelper::TextDataObject( sText ); + SolarMutexReleaser aReleaser; + xClipboard->setContents( pDataObj, nullptr ); + + Reference< datatransfer::clipboard::XFlushableClipboard > xFlushableClipboard( xClipboard, uno::UNO_QUERY ); + if( xFlushableClipboard.is() ) + xFlushableClipboard->flushClipboard(); + + bReturn = true; + } + + + return bReturn; +} + +sal_Bool SAL_CALL SmGraphicAccessible::scrollSubstringTo( sal_Int32, sal_Int32, AccessibleScrollType ) +{ + return false; +} + +OUString SAL_CALL SmGraphicAccessible::getImplementationName() +{ + return "SmGraphicAccessible"; +} + +sal_Bool SAL_CALL SmGraphicAccessible::supportsService( + const OUString& rServiceName ) +{ + return cppu::supportsService(this, rServiceName); +} + +Sequence< OUString > SAL_CALL SmGraphicAccessible::getSupportedServiceNames() +{ + return { + "css::accessibility::Accessible", + "css::accessibility::AccessibleComponent", + "css::accessibility::AccessibleContext", + "css::accessibility::AccessibleText" + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/accessibility.hxx b/starmath/source/accessibility.hxx new file mode 100644 index 0000000000..fd41e5a321 --- /dev/null +++ b/starmath/source/accessibility.hxx @@ -0,0 +1,134 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <com/sun/star/accessibility/AccessibleScrollType.hpp> +#include <com/sun/star/accessibility/XAccessible.hpp> +#include <com/sun/star/accessibility/XAccessibleComponent.hpp> +#include <com/sun/star/accessibility/XAccessibleContext.hpp> +#include <com/sun/star/accessibility/XAccessibleText.hpp> +#include <com/sun/star/accessibility/XAccessibleEventBroadcaster.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/uno/Reference.h> +#include <cppuhelper/implbase.hxx> + +#include <view.hxx> + +class SmDocShell; + +namespace accessibility { class AccessibleTextHelper; } + +// classes and helper-classes used for accessibility in the graphic-window + + +typedef +cppu::WeakImplHelper + < + css::lang::XServiceInfo, + css::accessibility::XAccessible, + css::accessibility::XAccessibleComponent, + css::accessibility::XAccessibleContext, + css::accessibility::XAccessibleText, + css::accessibility::XAccessibleEventBroadcaster + > +SmGraphicAccessibleBaseClass; + +class SmGraphicAccessible final : + public SmGraphicAccessibleBaseClass +{ + OUString aAccName; + /// client id in the AccessibleEventNotifier queue + sal_uInt32 nClientId; + + SmGraphicWidget* pWin; + + SmGraphicAccessible( const SmGraphicAccessible & ) = delete; + SmGraphicAccessible & operator = ( const SmGraphicAccessible & ) = delete; + + SmDocShell * GetDoc_Impl(); + OUString GetAccessibleText_Impl(); + +public: + explicit SmGraphicAccessible( SmGraphicWidget *pGraphicWin ); + virtual ~SmGraphicAccessible() override; + + void ClearWin(); // to be called when view is destroyed + void LaunchEvent( + const sal_Int16 nAccessibleEventId, + const css::uno::Any &rOldVal, + const css::uno::Any &rNewVal); + + // XAccessible + virtual css::uno::Reference< css::accessibility::XAccessibleContext > SAL_CALL getAccessibleContext( ) override; + + // XAccessibleComponent + virtual sal_Bool SAL_CALL containsPoint( const css::awt::Point& aPoint ) override; + virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL getAccessibleAtPoint( const css::awt::Point& aPoint ) override; + virtual css::awt::Rectangle SAL_CALL getBounds( ) override; + virtual css::awt::Point SAL_CALL getLocation( ) override; + virtual css::awt::Point SAL_CALL getLocationOnScreen( ) override; + virtual css::awt::Size SAL_CALL getSize( ) override; + virtual void SAL_CALL grabFocus( ) override; + virtual sal_Int32 SAL_CALL getForeground( ) override; + virtual sal_Int32 SAL_CALL getBackground( ) override; + + // XAccessibleContext + virtual sal_Int64 SAL_CALL getAccessibleChildCount( ) override; + virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL getAccessibleChild( sal_Int64 i ) override; + virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL getAccessibleParent( ) override; + virtual sal_Int64 SAL_CALL getAccessibleIndexInParent( ) override; + virtual sal_Int16 SAL_CALL getAccessibleRole( ) override; + virtual OUString SAL_CALL getAccessibleDescription( ) override; + virtual OUString SAL_CALL getAccessibleName( ) override; + virtual css::uno::Reference< css::accessibility::XAccessibleRelationSet > SAL_CALL getAccessibleRelationSet( ) override; + virtual sal_Int64 SAL_CALL getAccessibleStateSet( ) override; + virtual css::lang::Locale SAL_CALL getLocale( ) override; + + // XAccessibleEventBroadcaster + virtual void SAL_CALL addAccessibleEventListener( const css::uno::Reference< css::accessibility::XAccessibleEventListener >& xListener ) override; + virtual void SAL_CALL removeAccessibleEventListener( const css::uno::Reference< css::accessibility::XAccessibleEventListener >& xListener ) override; + + // XAccessibleText + virtual sal_Int32 SAL_CALL getCaretPosition( ) override; + virtual sal_Bool SAL_CALL setCaretPosition ( sal_Int32 nIndex ) override; + virtual sal_Unicode SAL_CALL getCharacter( sal_Int32 nIndex ) override; + virtual css::uno::Sequence< css::beans::PropertyValue > SAL_CALL getCharacterAttributes( sal_Int32 nIndex, const css::uno::Sequence< OUString >& aRequestedAttributes ) override; + virtual css::awt::Rectangle SAL_CALL getCharacterBounds( sal_Int32 nIndex ) override; + virtual sal_Int32 SAL_CALL getCharacterCount( ) override; + virtual sal_Int32 SAL_CALL getIndexAtPoint( const css::awt::Point& aPoint ) override; + virtual OUString SAL_CALL getSelectedText( ) override; + virtual sal_Int32 SAL_CALL getSelectionStart( ) override; + virtual sal_Int32 SAL_CALL getSelectionEnd( ) override; + virtual sal_Bool SAL_CALL setSelection( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) override; + virtual OUString SAL_CALL getText( ) override; + virtual OUString SAL_CALL getTextRange( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) override; + virtual css::accessibility::TextSegment SAL_CALL getTextAtIndex( sal_Int32 nIndex, sal_Int16 aTextType ) override; + virtual css::accessibility::TextSegment SAL_CALL getTextBeforeIndex( sal_Int32 nIndex, sal_Int16 aTextType ) override; + virtual css::accessibility::TextSegment SAL_CALL getTextBehindIndex( sal_Int32 nIndex, sal_Int16 aTextType ) override; + virtual sal_Bool SAL_CALL copyText( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) override; + virtual sal_Bool SAL_CALL scrollSubstringTo( sal_Int32 nStartIndex, sal_Int32 nEndIndex, css::accessibility::AccessibleScrollType aScrollType) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/action.cxx b/starmath/source/action.cxx new file mode 100644 index 0000000000..37e643c85c --- /dev/null +++ b/starmath/source/action.cxx @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <action.hxx> +#include <document.hxx> +#include <strings.hxx> + +SmFormatAction::SmFormatAction(SmDocShell *pDocSh, + const SmFormat& rOldFormat, + const SmFormat& rNewFormat) : + pDoc( pDocSh ), + aOldFormat( rOldFormat ), + aNewFormat( rNewFormat ) +{ +} + +void SmFormatAction::Undo() +{ + pDoc->SetFormat(aOldFormat); +} + +void SmFormatAction::Redo() +{ + pDoc->SetFormat(aNewFormat); +} + +void SmFormatAction::Repeat(SfxRepeatTarget& rDocSh) +{ + dynamic_cast< SmDocShell & >(rDocSh).SetFormat(aNewFormat); +} + +OUString SmFormatAction::GetComment() const +{ + return RID_UNDOFORMATNAME; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/caret.cxx b/starmath/source/caret.cxx new file mode 100644 index 0000000000..4e2effaa23 --- /dev/null +++ b/starmath/source/caret.cxx @@ -0,0 +1,28 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +#include <caret.hxx> + +SmCaretPosGraph::SmCaretPosGraph() = default; + +SmCaretPosGraph::~SmCaretPosGraph() = default; + +SmCaretPosGraphEntry* SmCaretPosGraph::Add(SmCaretPos pos, + SmCaretPosGraphEntry* left) +{ + assert(pos.nIndex >= 0); + auto entry = std::make_unique<SmCaretPosGraphEntry>(pos, left, nullptr); + SmCaretPosGraphEntry* e = entry.get(); + //Set Left and Right to point to the entry itself if they are NULL + entry->Left = entry->Left ? entry->Left : e; + entry->Right = entry->Right ? entry->Right : e; + mvEntries.push_back(std::move(entry)); + return e; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/cfgitem.cxx b/starmath/source/cfgitem.cxx new file mode 100644 index 0000000000..62918828fd --- /dev/null +++ b/starmath/source/cfgitem.cxx @@ -0,0 +1,1474 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <svl/itemset.hxx> +#include <svl/intitem.hxx> +#include <svl/eitem.hxx> +#include <svl/languageoptions.hxx> +#include <unotools/configmgr.hxx> +#include <utility> +#include <vcl/outdev.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <i18nlangtag/languagetag.hxx> + +#include <com/sun/star/beans/PropertyValue.hpp> + +#include <cfgitem.hxx> + +#include <starmath.hrc> +#include <smmod.hxx> +#include <symbol.hxx> +#include <format.hxx> + +using namespace com::sun::star::uno; +using namespace com::sun::star::beans; + +constexpr OUString SYMBOL_LIST = u"SymbolList"_ustr; +constexpr OUString FONT_FORMAT_LIST = u"FontFormatList"_ustr; + +static Sequence< OUString > lcl_GetFontPropertyNames() +{ + return Sequence< OUString > { + "Name", + "CharSet", + "Family", + "Pitch", + "Weight", + "Italic" + }; +} + +static Sequence< OUString > lcl_GetSymbolPropertyNames() +{ + return Sequence< OUString > { + "Char", + "Set", + "Predefined", + "FontFormatId" + }; +} + +static Sequence<OUString> lcl_GetOtherPropertyNames() +{ + return Sequence<OUString>{ "LoadSave/IsSaveOnlyUsedSymbols", + "Misc/AutoCloseBrackets", + "Misc/DefaultSmSyntaxVersion", + "Misc/IgnoreSpacesRight", + "Misc/InlineEditEnable", + "Misc/SmEditWindowZoomFactor", + "Print/FormulaText", + "Print/Frame", + "Print/Size", + "Print/Title", + "Print/ZoomFactor", + "View/AutoRedraw", + "View/FormulaCursor", + "View/ToolboxVisible" }; +} + +static Sequence< OUString > lcl_GetFormatPropertyNames() +{ + //! Beware of order according to *_BEGIN *_END defines in format.hxx ! + //! see respective load/save routines here + return Sequence< OUString > { + "StandardFormat/Textmode", + "StandardFormat/RightToLeft", + "StandardFormat/GreekCharStyle", + "StandardFormat/ScaleNormalBracket", + "StandardFormat/HorizontalAlignment", + "StandardFormat/BaseSize", + "StandardFormat/TextSize", + "StandardFormat/IndexSize", + "StandardFormat/FunctionSize", + "StandardFormat/OperatorSize", + "StandardFormat/LimitsSize", + "StandardFormat/Distance/Horizontal", + "StandardFormat/Distance/Vertical", + "StandardFormat/Distance/Root", + "StandardFormat/Distance/SuperScript", + "StandardFormat/Distance/SubScript", + "StandardFormat/Distance/Numerator", + "StandardFormat/Distance/Denominator", + "StandardFormat/Distance/Fraction", + "StandardFormat/Distance/StrokeWidth", + "StandardFormat/Distance/UpperLimit", + "StandardFormat/Distance/LowerLimit", + "StandardFormat/Distance/BracketSize", + "StandardFormat/Distance/BracketSpace", + "StandardFormat/Distance/MatrixRow", + "StandardFormat/Distance/MatrixColumn", + "StandardFormat/Distance/OrnamentSize", + "StandardFormat/Distance/OrnamentSpace", + "StandardFormat/Distance/OperatorSize", + "StandardFormat/Distance/OperatorSpace", + "StandardFormat/Distance/LeftSpace", + "StandardFormat/Distance/RightSpace", + "StandardFormat/Distance/TopSpace", + "StandardFormat/Distance/BottomSpace", + "StandardFormat/Distance/NormalBracketSize", + "StandardFormat/VariableFont", + "StandardFormat/FunctionFont", + "StandardFormat/NumberFont", + "StandardFormat/TextFont", + "StandardFormat/SerifFont", + "StandardFormat/SansFont", + "StandardFormat/FixedFont" + }; +} + +struct SmCfgOther +{ + SmPrintSize ePrintSize; + sal_uInt16 nPrintZoomFactor; + sal_uInt16 nSmEditWindowZoomFactor; + sal_Int16 nSmSyntaxVersion; + bool bPrintTitle; + bool bPrintFormulaText; + bool bPrintFrame; + bool bIsSaveOnlyUsedSymbols; + bool bIsAutoCloseBrackets; + bool bInlineEditEnable; + bool bIgnoreSpacesRight; + bool bToolboxVisible; + bool bAutoRedraw; + bool bFormulaCursor; + + SmCfgOther(); +}; + +constexpr sal_Int16 nDefaultSmSyntaxVersion(5); + +SmCfgOther::SmCfgOther() + : ePrintSize(PRINT_SIZE_NORMAL) + , nPrintZoomFactor(100) + , nSmEditWindowZoomFactor(100) + // Defaulted as 5 so I have time to code the parser 6 + , nSmSyntaxVersion(nDefaultSmSyntaxVersion) + , bPrintTitle(true) + , bPrintFormulaText(true) + , bPrintFrame(true) + , bIsSaveOnlyUsedSymbols(true) + , bIsAutoCloseBrackets(true) + , bInlineEditEnable(true) + , bIgnoreSpacesRight(true) + , bToolboxVisible(true) + , bAutoRedraw(true) + , bFormulaCursor(true) +{ +} + + +SmFontFormat::SmFontFormat() + : aName(FONTNAME_MATH) + , nCharSet(RTL_TEXTENCODING_UNICODE) + , nFamily(FAMILY_DONTKNOW) + , nPitch(PITCH_DONTKNOW) + , nWeight(WEIGHT_DONTKNOW) + , nItalic(ITALIC_NONE) +{ +} + + +SmFontFormat::SmFontFormat( const vcl::Font &rFont ) + : aName(rFont.GetFamilyName()) + , nCharSet(static_cast<sal_Int16>(rFont.GetCharSet())) + , nFamily(static_cast<sal_Int16>(rFont.GetFamilyType())) + , nPitch(static_cast<sal_Int16>(rFont.GetPitch())) + , nWeight(static_cast<sal_Int16>(rFont.GetWeight())) + , nItalic(static_cast<sal_Int16>(rFont.GetItalic())) +{ +} + + +vcl::Font SmFontFormat::GetFont() const +{ + vcl::Font aRes; + aRes.SetFamilyName( aName ); + aRes.SetCharSet( static_cast<rtl_TextEncoding>(nCharSet) ); + aRes.SetFamily( static_cast<FontFamily>(nFamily) ); + aRes.SetPitch( static_cast<FontPitch>(nPitch) ); + aRes.SetWeight( static_cast<FontWeight>(nWeight) ); + aRes.SetItalic( static_cast<FontItalic>(nItalic) ); + return aRes; +} + + +bool SmFontFormat::operator == ( const SmFontFormat &rFntFmt ) const +{ + return aName == rFntFmt.aName && + nCharSet == rFntFmt.nCharSet && + nFamily == rFntFmt.nFamily && + nPitch == rFntFmt.nPitch && + nWeight == rFntFmt.nWeight && + nItalic == rFntFmt.nItalic; +} + + +SmFntFmtListEntry::SmFntFmtListEntry( OUString _aId, SmFontFormat _aFntFmt ) : + aId (std::move(_aId)), + aFntFmt (std::move(_aFntFmt)) +{ +} + + +SmFontFormatList::SmFontFormatList() + : bModified(false) +{ +} + + +void SmFontFormatList::Clear() +{ + if (!aEntries.empty()) + { + aEntries.clear(); + SetModified( true ); + } +} + + +void SmFontFormatList::AddFontFormat( const OUString &rFntFmtId, + const SmFontFormat &rFntFmt ) +{ + const SmFontFormat *pFntFmt = GetFontFormat( rFntFmtId ); + OSL_ENSURE( !pFntFmt, "FontFormatId already exists" ); + if (!pFntFmt) + { + SmFntFmtListEntry aEntry( rFntFmtId, rFntFmt ); + aEntries.push_back( aEntry ); + SetModified( true ); + } +} + + +void SmFontFormatList::RemoveFontFormat( std::u16string_view rFntFmtId ) +{ + + // search for entry + for (size_t i = 0; i < aEntries.size(); ++i) + { + if (aEntries[i].aId == rFntFmtId) + { + // remove entry if found + aEntries.erase( aEntries.begin() + i ); + SetModified( true ); + break; + } + } +} + + +const SmFontFormat * SmFontFormatList::GetFontFormat( std::u16string_view rFntFmtId ) const +{ + const SmFontFormat *pRes = nullptr; + + for (const auto & rEntry : aEntries) + { + if (rEntry.aId == rFntFmtId) + { + pRes = &rEntry.aFntFmt; + break; + } + } + + return pRes; +} + + +const SmFontFormat * SmFontFormatList::GetFontFormat( size_t nPos ) const +{ + const SmFontFormat *pRes = nullptr; + if (nPos < aEntries.size()) + pRes = &aEntries[nPos].aFntFmt; + return pRes; +} + + +OUString SmFontFormatList::GetFontFormatId( const SmFontFormat &rFntFmt ) const +{ + OUString aRes; + + for (const auto & rEntry : aEntries) + { + if (rEntry.aFntFmt == rFntFmt) + { + aRes = rEntry.aId; + break; + } + } + + return aRes; +} + + +OUString SmFontFormatList::GetFontFormatId( const SmFontFormat &rFntFmt, bool bAdd ) +{ + OUString aRes( GetFontFormatId( rFntFmt) ); + if (aRes.isEmpty() && bAdd) + { + aRes = GetNewFontFormatId(); + AddFontFormat( aRes, rFntFmt ); + } + return aRes; +} + + +OUString SmFontFormatList::GetFontFormatId( size_t nPos ) const +{ + OUString aRes; + if (nPos < aEntries.size()) + aRes = aEntries[nPos].aId; + return aRes; +} + + +OUString SmFontFormatList::GetNewFontFormatId() const +{ + // returns first unused FormatId + + sal_Int32 nCnt = GetCount(); + for (sal_Int32 i = 1; i <= nCnt + 1; ++i) + { + OUString aTmpId = "Id" + OUString::number(i); + if (!GetFontFormat(aTmpId)) + return aTmpId; + } + OSL_ENSURE( false, "failed to create new FontFormatId" ); + + return OUString(); +} + + +SmMathConfig::SmMathConfig() : + ConfigItem("Office.Math") + , bIsOtherModified(false) + , bIsFormatModified(false) +{ + EnableNotification({ {} }); // Listen to everything under the node +} + + +SmMathConfig::~SmMathConfig() +{ + Save(); +} + + +void SmMathConfig::SetOtherModified( bool bVal ) +{ + bIsOtherModified = bVal; +} + + +void SmMathConfig::SetFormatModified( bool bVal ) +{ + bIsFormatModified = bVal; +} + + +void SmMathConfig::ReadSymbol( SmSym &rSymbol, + const OUString &rSymbolName, + std::u16string_view rBaseNode ) const +{ + Sequence< OUString > aNames = lcl_GetSymbolPropertyNames(); + sal_Int32 nProps = aNames.getLength(); + + OUString aDelim( "/" ); + for (auto& rName : asNonConstRange(aNames)) + rName = rBaseNode + aDelim + rSymbolName + aDelim + rName; + + const Sequence< Any > aValues = const_cast<SmMathConfig*>(this)->GetProperties(aNames); + + if (!(nProps && aValues.getLength() == nProps)) + return; + + const Any * pValue = aValues.getConstArray(); + vcl::Font aFont; + sal_UCS4 cChar = '\0'; + OUString aSet; + bool bPredefined = false; + + OUString aTmpStr; + sal_Int32 nTmp32 = 0; + bool bTmp = false; + + bool bOK = true; + if (pValue->hasValue() && (*pValue >>= nTmp32)) + cChar = static_cast< sal_UCS4 >( nTmp32 ); + else + bOK = false; + ++pValue; + if (pValue->hasValue() && (*pValue >>= aTmpStr)) + aSet = aTmpStr; + else + bOK = false; + ++pValue; + if (pValue->hasValue() && (*pValue >>= bTmp)) + bPredefined = bTmp; + else + bOK = false; + ++pValue; + if (pValue->hasValue() && (*pValue >>= aTmpStr)) + { + const SmFontFormat *pFntFmt = GetFontFormatList().GetFontFormat( aTmpStr ); + OSL_ENSURE( pFntFmt, "unknown FontFormat" ); + if (pFntFmt) + aFont = pFntFmt->GetFont(); + } + ++pValue; + + if (bOK) + { + OUString aUiName( rSymbolName ); + OUString aUiSetName( aSet ); + if (bPredefined) + { + OUString aTmp; + aTmp = SmLocalizedSymbolData::GetUiSymbolName( rSymbolName ); + OSL_ENSURE( !aTmp.isEmpty(), "localized symbol-name not found" ); + if (!aTmp.isEmpty()) + aUiName = aTmp; + aTmp = SmLocalizedSymbolData::GetUiSymbolSetName( aSet ); + OSL_ENSURE( !aTmp.isEmpty(), "localized symbolset-name not found" ); + if (!aTmp.isEmpty()) + aUiSetName = aTmp; + } + + rSymbol = SmSym( aUiName, aFont, cChar, aUiSetName, bPredefined ); + if (aUiName != rSymbolName) + rSymbol.SetExportName( rSymbolName ); + } + else + { + SAL_WARN("starmath", "symbol read error"); + } +} + + +SmSymbolManager & SmMathConfig::GetSymbolManager() +{ + if (!pSymbolMgr) + { + pSymbolMgr.reset(new SmSymbolManager); + pSymbolMgr->Load(); + } + return *pSymbolMgr; +} + + +void SmMathConfig::ImplCommit() +{ + Save(); +} + + +void SmMathConfig::Save() +{ + SaveOther(); + SaveFormat(); + SaveFontFormatList(); +} + + +void SmMathConfig::UnlockCommit() +{ + if (--m_nCommitLock == 0) + Commit(); +} + + +void SmMathConfig::Clear() +{ + // Re-read data on next request + pOther.reset(); + pFormat.reset(); + pFontFormatList.reset(); +} + + +void SmMathConfig::GetSymbols( std::vector< SmSym > &rSymbols ) const +{ + Sequence< OUString > aNodes(const_cast<SmMathConfig*>(this)->GetNodeNames(SYMBOL_LIST)); + const OUString *pNode = aNodes.getConstArray(); + sal_Int32 nNodes = aNodes.getLength(); + + rSymbols.resize( nNodes ); + for (auto& rSymbol : rSymbols) + { + ReadSymbol( rSymbol, *pNode++, SYMBOL_LIST ); + } +} + + +void SmMathConfig::SetSymbols( const std::vector< SmSym > &rNewSymbols ) +{ + CommitLocker aLock(*this); + auto nCount = sal::static_int_cast<sal_Int32>(rNewSymbols.size()); + + Sequence< OUString > aNames = lcl_GetSymbolPropertyNames(); + const OUString *pNames = aNames.getConstArray(); + sal_Int32 nSymbolProps = aNames.getLength(); + + Sequence< PropertyValue > aValues( nCount * nSymbolProps ); + PropertyValue *pValues = aValues.getArray(); + + PropertyValue *pVal = pValues; + OUString aDelim( "/" ); + for (const SmSym& rSymbol : rNewSymbols) + { + OUString aNodeNameDelim = SYMBOL_LIST + + aDelim + + rSymbol.GetExportName() + + aDelim; + + const OUString *pName = pNames; + + // Char + pVal->Name = aNodeNameDelim; + pVal->Name += *pName++; + pVal->Value <<= rSymbol.GetCharacter(); + pVal++; + // Set + pVal->Name = aNodeNameDelim; + pVal->Name += *pName++; + OUString aTmp( rSymbol.GetSymbolSetName() ); + if (rSymbol.IsPredefined()) + aTmp = SmLocalizedSymbolData::GetExportSymbolSetName( aTmp ); + pVal->Value <<= aTmp; + pVal++; + // Predefined + pVal->Name = aNodeNameDelim; + pVal->Name += *pName++; + pVal->Value <<= rSymbol.IsPredefined(); + pVal++; + // FontFormatId + SmFontFormat aFntFmt( rSymbol.GetFace() ); + OUString aFntFmtId( GetFontFormatList().GetFontFormatId( aFntFmt, true ) ); + OSL_ENSURE( !aFntFmtId.isEmpty(), "FontFormatId not found" ); + pVal->Name = aNodeNameDelim; + pVal->Name += *pName++; + pVal->Value <<= aFntFmtId; + pVal++; + } + OSL_ENSURE( pVal - pValues == sal::static_int_cast< ptrdiff_t >(nCount * nSymbolProps), "properties missing" ); + ReplaceSetProperties( SYMBOL_LIST, aValues ); + + StripFontFormatList( rNewSymbols ); +} + + +SmFontFormatList & SmMathConfig::GetFontFormatList() +{ + if (!pFontFormatList) + { + LoadFontFormatList(); + } + return *pFontFormatList; +} + + +void SmMathConfig::LoadFontFormatList() +{ + if (!pFontFormatList) + pFontFormatList.reset(new SmFontFormatList); + else + pFontFormatList->Clear(); + + const Sequence< OUString > aNodes( GetNodeNames( FONT_FORMAT_LIST ) ); + + for (const OUString& rNode : aNodes) + { + SmFontFormat aFntFmt; + ReadFontFormat( aFntFmt, rNode, FONT_FORMAT_LIST ); + if (!pFontFormatList->GetFontFormat( rNode )) + pFontFormatList->AddFontFormat( rNode, aFntFmt ); + } + pFontFormatList->SetModified( false ); +} + + +void SmMathConfig::ReadFontFormat( SmFontFormat &rFontFormat, + std::u16string_view rSymbolName, std::u16string_view rBaseNode ) const +{ + Sequence< OUString > aNames = lcl_GetFontPropertyNames(); + sal_Int32 nProps = aNames.getLength(); + + OUString aDelim( "/" ); + for (auto& rName : asNonConstRange(aNames)) + rName = rBaseNode + aDelim + rSymbolName + aDelim + rName; + + const Sequence< Any > aValues = const_cast<SmMathConfig*>(this)->GetProperties(aNames); + + if (!(nProps && aValues.getLength() == nProps)) + return; + + const Any * pValue = aValues.getConstArray(); + + OUString aTmpStr; + sal_Int16 nTmp16 = 0; + + bool bOK = true; + if (pValue->hasValue() && (*pValue >>= aTmpStr)) + rFontFormat.aName = aTmpStr; + else + bOK = false; + ++pValue; + if (pValue->hasValue() && (*pValue >>= nTmp16)) + rFontFormat.nCharSet = nTmp16; // 6.0 file-format GetSOLoadTextEncoding not needed + else + bOK = false; + ++pValue; + if (pValue->hasValue() && (*pValue >>= nTmp16)) + rFontFormat.nFamily = nTmp16; + else + bOK = false; + ++pValue; + if (pValue->hasValue() && (*pValue >>= nTmp16)) + rFontFormat.nPitch = nTmp16; + else + bOK = false; + ++pValue; + if (pValue->hasValue() && (*pValue >>= nTmp16)) + rFontFormat.nWeight = nTmp16; + else + bOK = false; + ++pValue; + if (pValue->hasValue() && (*pValue >>= nTmp16)) + rFontFormat.nItalic = nTmp16; + else + bOK = false; + ++pValue; + + OSL_ENSURE( bOK, "read FontFormat failed" ); +} + + +void SmMathConfig::SaveFontFormatList() +{ + SmFontFormatList &rFntFmtList = GetFontFormatList(); + + if (!rFntFmtList.IsModified()) + return; + + Sequence< OUString > aNames = lcl_GetFontPropertyNames(); + sal_Int32 nSymbolProps = aNames.getLength(); + + size_t nCount = rFntFmtList.GetCount(); + + Sequence< PropertyValue > aValues( nCount * nSymbolProps ); + PropertyValue *pValues = aValues.getArray(); + + PropertyValue *pVal = pValues; + OUString aDelim( "/" ); + for (size_t i = 0; i < nCount; ++i) + { + OUString aFntFmtId(rFntFmtList.GetFontFormatId(i)); + const SmFontFormat *pFntFmt = rFntFmtList.GetFontFormat(i); + assert(pFntFmt); + const SmFontFormat aFntFmt(*pFntFmt); + + OUString aNodeNameDelim = FONT_FORMAT_LIST + + aDelim + + aFntFmtId + + aDelim; + + const OUString *pName = aNames.getConstArray(); + + // Name + pVal->Name = aNodeNameDelim; + pVal->Name += *pName++; + pVal->Value <<= aFntFmt.aName; + pVal++; + // CharSet + pVal->Name = aNodeNameDelim; + pVal->Name += *pName++; + pVal->Value <<= aFntFmt.nCharSet; // 6.0 file-format GetSOStoreTextEncoding not needed + pVal++; + // Family + pVal->Name = aNodeNameDelim; + pVal->Name += *pName++; + pVal->Value <<= aFntFmt.nFamily; + pVal++; + // Pitch + pVal->Name = aNodeNameDelim; + pVal->Name += *pName++; + pVal->Value <<= aFntFmt.nPitch; + pVal++; + // Weight + pVal->Name = aNodeNameDelim; + pVal->Name += *pName++; + pVal->Value <<= aFntFmt.nWeight; + pVal++; + // Italic + pVal->Name = aNodeNameDelim; + pVal->Name += *pName++; + pVal->Value <<= aFntFmt.nItalic; + pVal++; + } + OSL_ENSURE( sal::static_int_cast<size_t>(pVal - pValues) == nCount * nSymbolProps, "properties missing" ); + ReplaceSetProperties( FONT_FORMAT_LIST, aValues ); + + rFntFmtList.SetModified( false ); +} + + +void SmMathConfig::StripFontFormatList( const std::vector< SmSym > &rSymbols ) +{ + size_t i; + + // build list of used font-formats only + //!! font-format IDs may be different !! + SmFontFormatList aUsedList; + for (i = 0; i < rSymbols.size(); ++i) + { + OSL_ENSURE( !rSymbols[i].GetUiName().isEmpty(), "non named symbol" ); + aUsedList.GetFontFormatId( SmFontFormat( rSymbols[i].GetFace() ) , true ); + } + const SmFormat & rStdFmt = GetStandardFormat(); + for (i = FNT_BEGIN; i <= FNT_END; ++i) + { + aUsedList.GetFontFormatId( SmFontFormat( rStdFmt.GetFont( i ) ) , true ); + } + + // remove unused font-formats from list + SmFontFormatList &rFntFmtList = GetFontFormatList(); + size_t nCnt = rFntFmtList.GetCount(); + std::unique_ptr<SmFontFormat[]> pTmpFormat(new SmFontFormat[ nCnt ]); + std::unique_ptr<OUString[]> pId(new OUString[ nCnt ]); + size_t k; + for (k = 0; k < nCnt; ++k) + { + pTmpFormat[k] = *rFntFmtList.GetFontFormat( k ); + pId[k] = rFntFmtList.GetFontFormatId( k ); + } + for (k = 0; k < nCnt; ++k) + { + if (aUsedList.GetFontFormatId( pTmpFormat[k] ).isEmpty()) + { + rFntFmtList.RemoveFontFormat( pId[k] ); + } + } +} + + +void SmMathConfig::LoadOther() +{ + if (!pOther) + pOther.reset(new SmCfgOther); + + const Sequence<OUString> aNames(lcl_GetOtherPropertyNames()); + const Sequence<Any> aValues(GetProperties(aNames)); + if (aNames.getLength() != aValues.getLength()) + return; + + const Any* pValues = aValues.getConstArray(); + const Any* pVal = pValues; + + // LoadSave/IsSaveOnlyUsedSymbols + if (bool bTmp; pVal->hasValue() && (*pVal >>= bTmp)) + pOther->bIsSaveOnlyUsedSymbols = bTmp; + ++pVal; + // Misc/AutoCloseBrackets + if (bool bTmp; pVal->hasValue() && (*pVal >>= bTmp)) + pOther->bIsAutoCloseBrackets = bTmp; + ++pVal; + // Misc/DefaultSmSyntaxVersion + if (sal_Int16 nTmp; pVal->hasValue() && (*pVal >>= nTmp)) + pOther->nSmSyntaxVersion = nTmp; + ++pVal; + // Misc/InlineEditEnable + if (bool bTmp; pVal->hasValue() && (*pVal >>= bTmp)) + pOther->bInlineEditEnable = bTmp; + ++pVal; + // Misc/IgnoreSpacesRight + if (bool bTmp; pVal->hasValue() && (*pVal >>= bTmp)) + pOther->bIgnoreSpacesRight = bTmp; + ++pVal; + // Misc/SmEditWindowZoomFactor + if (sal_Int16 nTmp; pVal->hasValue() && (*pVal >>= nTmp)) + pOther->nSmEditWindowZoomFactor = nTmp; + ++pVal; + // Print/FormulaText + if (bool bTmp; pVal->hasValue() && (*pVal >>= bTmp)) + pOther->bPrintFormulaText = bTmp; + ++pVal; + // Print/Frame + if (bool bTmp; pVal->hasValue() && (*pVal >>= bTmp)) + pOther->bPrintFrame = bTmp; + ++pVal; + // Print/Size: + if (sal_Int16 nTmp; pVal->hasValue() && (*pVal >>= nTmp)) + pOther->ePrintSize = static_cast<SmPrintSize>(nTmp); + ++pVal; + // Print/Title + if (bool bTmp; pVal->hasValue() && (*pVal >>= bTmp)) + pOther->bPrintTitle = bTmp; + ++pVal; + // Print/ZoomFactor + if (sal_Int16 nTmp; pVal->hasValue() && (*pVal >>= nTmp)) + pOther->nPrintZoomFactor = nTmp; + ++pVal; + // View/AutoRedraw + if (bool bTmp; pVal->hasValue() && (*pVal >>= bTmp)) + pOther->bAutoRedraw = bTmp; + ++pVal; + // View/FormulaCursor + if (bool bTmp; pVal->hasValue() && (*pVal >>= bTmp)) + pOther->bFormulaCursor = bTmp; + ++pVal; + // View/ToolboxVisible + if (bool bTmp; pVal->hasValue() && (*pVal >>= bTmp)) + pOther->bToolboxVisible = bTmp; + ++pVal; + + OSL_ENSURE(pVal - pValues == aNames.getLength(), "property mismatch"); + SetOtherModified( false ); +} + + +void SmMathConfig::SaveOther() +{ + if (!pOther || !IsOtherModified()) + return; + + const Sequence<OUString> aNames(lcl_GetOtherPropertyNames()); + Sequence<Any> aValues(aNames.getLength()); + + Any* pValues = aValues.getArray(); + Any* pVal = pValues; + + // LoadSave/IsSaveOnlyUsedSymbols + *pVal++ <<= pOther->bIsSaveOnlyUsedSymbols; + // Misc/AutoCloseBrackets + *pVal++ <<= pOther->bIsAutoCloseBrackets; + // Misc/DefaultSmSyntaxVersion + *pVal++ <<= pOther->nSmSyntaxVersion; + // Misc/InlineEditEnable + *pVal++ <<= pOther->bInlineEditEnable; + // Misc/IgnoreSpacesRight + *pVal++ <<= pOther->bIgnoreSpacesRight; + // Misc/SmEditWindowZoomFactor + *pVal++ <<= pOther->nSmEditWindowZoomFactor; + // Print/FormulaText + *pVal++ <<= pOther->bPrintFormulaText; + // Print/Frame + *pVal++ <<= pOther->bPrintFrame; + // Print/Size: + *pVal++ <<= static_cast<sal_Int16>(pOther->ePrintSize); + // Print/Title + *pVal++ <<= pOther->bPrintTitle; + // Print/ZoomFactor + *pVal++ <<= pOther->nPrintZoomFactor; + // View/AutoRedraw + *pVal++ <<= pOther->bAutoRedraw; + // View/FormulaCursor + *pVal++ <<= pOther->bFormulaCursor; + // View/ToolboxVisible + *pVal++ <<= pOther->bToolboxVisible; + + OSL_ENSURE(pVal - pValues == aNames.getLength(), "property mismatch"); + PutProperties(aNames, aValues); + + SetOtherModified( false ); +} + +namespace { + +// Latin default-fonts +const DefaultFontType aLatinDefFnts[FNT_END] = +{ + DefaultFontType::SERIF, // FNT_VARIABLE + DefaultFontType::SERIF, // FNT_FUNCTION + DefaultFontType::SERIF, // FNT_NUMBER + DefaultFontType::SERIF, // FNT_TEXT + DefaultFontType::SERIF, // FNT_SERIF + DefaultFontType::SANS, // FNT_SANS + DefaultFontType::FIXED // FNT_FIXED + //OpenSymbol, // FNT_MATH +}; + +// CJK default-fonts +//! we use non-asian fonts for variables, functions and numbers since they +//! look better and even in asia only latin letters will be used for those. +//! At least that's what I was told... +const DefaultFontType aCJKDefFnts[FNT_END] = +{ + DefaultFontType::SERIF, // FNT_VARIABLE + DefaultFontType::SERIF, // FNT_FUNCTION + DefaultFontType::SERIF, // FNT_NUMBER + DefaultFontType::CJK_TEXT, // FNT_TEXT + DefaultFontType::CJK_TEXT, // FNT_SERIF + DefaultFontType::CJK_DISPLAY, // FNT_SANS + DefaultFontType::CJK_TEXT // FNT_FIXED + //OpenSymbol, // FNT_MATH +}; + +// CTL default-fonts +const DefaultFontType aCTLDefFnts[FNT_END] = +{ + DefaultFontType::CTL_TEXT, // FNT_VARIABLE + DefaultFontType::CTL_TEXT, // FNT_FUNCTION + DefaultFontType::CTL_TEXT, // FNT_NUMBER + DefaultFontType::CTL_TEXT, // FNT_TEXT + DefaultFontType::CTL_TEXT, // FNT_SERIF + DefaultFontType::CTL_TEXT, // FNT_SANS + DefaultFontType::CTL_TEXT // FNT_FIXED + //OpenSymbol, // FNT_MATH +}; + + +OUString lcl_GetDefaultFontName( LanguageType nLang, sal_uInt16 nIdent ) +{ + assert(nIdent < FNT_END); + const DefaultFontType *pTable; + switch ( SvtLanguageOptions::GetScriptTypeOfLanguage( nLang ) ) + { + case SvtScriptType::LATIN : pTable = aLatinDefFnts; break; + case SvtScriptType::ASIAN : pTable = aCJKDefFnts; break; + case SvtScriptType::COMPLEX : pTable = aCTLDefFnts; break; + default : + pTable = aLatinDefFnts; + SAL_WARN("starmath", "unknown script-type"); + } + + return OutputDevice::GetDefaultFont(pTable[ nIdent ], nLang, + GetDefaultFontFlags::OnlyOne ).GetFamilyName(); +} + +} + + +void SmMathConfig::LoadFormat() +{ + if (!pFormat) + pFormat.reset(new SmFormat); + + + Sequence< OUString > aNames = lcl_GetFormatPropertyNames(); + + sal_Int32 nProps = aNames.getLength(); + + Sequence< Any > aValues( GetProperties( aNames ) ); + if (!(nProps && aValues.getLength() == nProps)) + return; + + const Any *pValues = aValues.getConstArray(); + const Any *pVal = pValues; + + OUString aTmpStr; + sal_Int16 nTmp16 = 0; + bool bTmp = false; + + // StandardFormat/Textmode + if (pVal->hasValue() && (*pVal >>= bTmp)) + pFormat->SetTextmode( bTmp ); + ++pVal; + // StandardFormat/RightToLeft + if (pVal->hasValue() && (*pVal >>= bTmp)) + pFormat->SetRightToLeft( bTmp ); + ++pVal; + // StandardFormat/GreekCharStyle + if (pVal->hasValue() && (*pVal >>= nTmp16)) + pFormat->SetGreekCharStyle( nTmp16 ); + ++pVal; + // StandardFormat/ScaleNormalBracket + if (pVal->hasValue() && (*pVal >>= bTmp)) + pFormat->SetScaleNormalBrackets( bTmp ); + ++pVal; + // StandardFormat/HorizontalAlignment + if (pVal->hasValue() && (*pVal >>= nTmp16)) + pFormat->SetHorAlign( static_cast<SmHorAlign>(nTmp16) ); + ++pVal; + // StandardFormat/BaseSize + if (pVal->hasValue() && (*pVal >>= nTmp16)) + pFormat->SetBaseSize(Size(0, o3tl::convert(nTmp16, o3tl::Length::pt, SmO3tlLengthUnit()))); + ++pVal; + + sal_uInt16 i; + for (i = SIZ_BEGIN; i <= SIZ_END; ++i) + { + if (pVal->hasValue() && (*pVal >>= nTmp16)) + pFormat->SetRelSize( i, nTmp16 ); + ++pVal; + } + + for (i = DIS_BEGIN; i <= DIS_END; ++i) + { + if (pVal->hasValue() && (*pVal >>= nTmp16)) + pFormat->SetDistance( i, nTmp16 ); + ++pVal; + } + + LanguageType nLang = Application::GetSettings().GetUILanguageTag().getLanguageType(); + for (i = FNT_BEGIN; i < FNT_END; ++i) + { + vcl::Font aFnt; + bool bUseDefaultFont = true; + if (pVal->hasValue() && (*pVal >>= aTmpStr)) + { + bUseDefaultFont = aTmpStr.isEmpty(); + if (bUseDefaultFont) + { + aFnt = pFormat->GetFont( i ); + aFnt.SetFamilyName( lcl_GetDefaultFontName( nLang, i ) ); + } + else + { + const SmFontFormat *pFntFmt = GetFontFormatList().GetFontFormat( aTmpStr ); + OSL_ENSURE( pFntFmt, "unknown FontFormat" ); + if (pFntFmt) + aFnt = pFntFmt->GetFont(); + } + } + ++pVal; + + aFnt.SetFontSize( pFormat->GetBaseSize() ); + pFormat->SetFont( i, aFnt, bUseDefaultFont ); + } + + OSL_ENSURE( pVal - pValues == nProps, "property mismatch" ); + SetFormatModified( false ); +} + + +void SmMathConfig::SaveFormat() +{ + if (!pFormat || !IsFormatModified()) + return; + + const Sequence< OUString > aNames = lcl_GetFormatPropertyNames(); + sal_Int32 nProps = aNames.getLength(); + + Sequence< Any > aValues( nProps ); + Any *pValues = aValues.getArray(); + Any *pValue = pValues; + + // StandardFormat/Textmode + *pValue++ <<= pFormat->IsTextmode(); + // StandardFormat/RightToLeft + *pValue++ <<= pFormat->IsRightToLeft(); + // StandardFormat/GreekCharStyle + *pValue++ <<= pFormat->GetGreekCharStyle(); + // StandardFormat/ScaleNormalBracket + *pValue++ <<= pFormat->IsScaleNormalBrackets(); + // StandardFormat/HorizontalAlignment + *pValue++ <<= static_cast<sal_Int16>(pFormat->GetHorAlign()); + // StandardFormat/BaseSize + *pValue++ <<= static_cast<sal_Int16>( + o3tl::convert(pFormat->GetBaseSize().Height(), SmO3tlLengthUnit(), o3tl::Length::pt)); + + sal_uInt16 i; + for (i = SIZ_BEGIN; i <= SIZ_END; ++i) + *pValue++ <<= static_cast<sal_Int16>(pFormat->GetRelSize( i )); + + for (i = DIS_BEGIN; i <= DIS_END; ++i) + *pValue++ <<= static_cast<sal_Int16>(pFormat->GetDistance( i )); + + for (i = FNT_BEGIN; i < FNT_END; ++i) + { + OUString aFntFmtId; + + if (!pFormat->IsDefaultFont( i )) + { + SmFontFormat aFntFmt( pFormat->GetFont( i ) ); + aFntFmtId = GetFontFormatList().GetFontFormatId( aFntFmt, true ); + OSL_ENSURE( !aFntFmtId.isEmpty(), "FontFormatId not found" ); + } + + *pValue++ <<= aFntFmtId; + } + + OSL_ENSURE( pValue - pValues == nProps, "property mismatch" ); + PutProperties( aNames , aValues ); + + SetFormatModified( false ); +} + + +const SmFormat & SmMathConfig::GetStandardFormat() const +{ + if (!pFormat) + const_cast<SmMathConfig*>(this)->LoadFormat(); + return *pFormat; +} + + +void SmMathConfig::SetStandardFormat( const SmFormat &rFormat, bool bSaveFontFormatList ) +{ + if (!pFormat) + LoadFormat(); + if (rFormat == *pFormat) + return; + + CommitLocker aLock(*this); + *pFormat = rFormat; + SetFormatModified( true ); + + if (bSaveFontFormatList) + { + // needed for SmFontTypeDialog's DefaultButtonClickHdl + if (pFontFormatList) + pFontFormatList->SetModified( true ); + } +} + + +SmPrintSize SmMathConfig::GetPrintSize() const +{ + if (!pOther) + const_cast<SmMathConfig*>(this)->LoadOther(); + return pOther->ePrintSize; +} + + +void SmMathConfig::SetPrintSize( SmPrintSize eSize ) +{ + if (!pOther) + LoadOther(); + if (eSize != pOther->ePrintSize) + { + CommitLocker aLock(*this); + pOther->ePrintSize = eSize; + SetOtherModified( true ); + } +} + + +sal_uInt16 SmMathConfig::GetPrintZoomFactor() const +{ + if (!pOther) + const_cast<SmMathConfig*>(this)->LoadOther(); + return pOther->nPrintZoomFactor; +} + + +void SmMathConfig::SetPrintZoomFactor( sal_uInt16 nVal ) +{ + if (!pOther) + LoadOther(); + if (nVal != pOther->nPrintZoomFactor) + { + CommitLocker aLock(*this); + pOther->nPrintZoomFactor = nVal; + SetOtherModified( true ); + } +} + + +sal_uInt16 SmMathConfig::GetSmEditWindowZoomFactor() const +{ + sal_uInt16 smzoomfactor; + if (!pOther) + const_cast<SmMathConfig*>(this)->LoadOther(); + smzoomfactor = pOther->nSmEditWindowZoomFactor; + return smzoomfactor < 10 || smzoomfactor > 1000 ? 100 : smzoomfactor; +} + + +void SmMathConfig::SetSmEditWindowZoomFactor( sal_uInt16 nVal ) +{ + if (!pOther) + LoadOther(); + if (nVal != pOther->nSmEditWindowZoomFactor) + { + CommitLocker aLock(*this); + pOther->nSmEditWindowZoomFactor = nVal; + SetOtherModified( true ); + } +} + + +bool SmMathConfig::SetOtherIfNotEqual( bool &rbItem, bool bNewVal ) +{ + if (bNewVal != rbItem) + { + CommitLocker aLock(*this); + rbItem = bNewVal; + SetOtherModified( true ); + return true; + } + return false; +} + + +bool SmMathConfig::IsPrintTitle() const +{ + if (!pOther) + const_cast<SmMathConfig*>(this)->LoadOther(); + return pOther->bPrintTitle; +} + + +void SmMathConfig::SetPrintTitle( bool bVal ) +{ + if (!pOther) + LoadOther(); + SetOtherIfNotEqual( pOther->bPrintTitle, bVal ); +} + + +bool SmMathConfig::IsPrintFormulaText() const +{ + if (!pOther) + const_cast<SmMathConfig*>(this)->LoadOther(); + return pOther->bPrintFormulaText; +} + + +void SmMathConfig::SetPrintFormulaText( bool bVal ) +{ + if (!pOther) + LoadOther(); + SetOtherIfNotEqual( pOther->bPrintFormulaText, bVal ); +} + +bool SmMathConfig::IsSaveOnlyUsedSymbols() const +{ + if (!pOther) + const_cast<SmMathConfig*>(this)->LoadOther(); + return pOther->bIsSaveOnlyUsedSymbols; +} + +bool SmMathConfig::IsAutoCloseBrackets() const +{ + if (!pOther) + const_cast<SmMathConfig*>(this)->LoadOther(); + return pOther->bIsAutoCloseBrackets; +} + +sal_Int16 SmMathConfig::GetDefaultSmSyntaxVersion() const +{ + if (utl::ConfigManager::IsFuzzing()) + return nDefaultSmSyntaxVersion; + if (!pOther) + const_cast<SmMathConfig*>(this)->LoadOther(); + return pOther->nSmSyntaxVersion; +} + +bool SmMathConfig::IsPrintFrame() const +{ + if (!pOther) + const_cast<SmMathConfig*>(this)->LoadOther(); + return pOther->bPrintFrame; +} + + +void SmMathConfig::SetPrintFrame( bool bVal ) +{ + if (!pOther) + LoadOther(); + SetOtherIfNotEqual( pOther->bPrintFrame, bVal ); +} + + +void SmMathConfig::SetSaveOnlyUsedSymbols( bool bVal ) +{ + if (!pOther) + LoadOther(); + SetOtherIfNotEqual( pOther->bIsSaveOnlyUsedSymbols, bVal ); +} + + +void SmMathConfig::SetAutoCloseBrackets( bool bVal ) +{ + if (!pOther) + LoadOther(); + SetOtherIfNotEqual( pOther->bIsAutoCloseBrackets, bVal ); +} + +void SmMathConfig::SetDefaultSmSyntaxVersion( sal_Int16 nVal ) +{ + if (!pOther) + LoadOther(); + if (nVal != pOther->nSmSyntaxVersion) + { + CommitLocker aLock(*this); + pOther->nSmSyntaxVersion = nVal; + SetOtherModified( true ); + } +} + +bool SmMathConfig::IsInlineEditEnable() const +{ + if (utl::ConfigManager::IsFuzzing()) + return false; + if (!pOther) + const_cast<SmMathConfig*>(this)->LoadOther(); + return pOther->bInlineEditEnable; +} + + +void SmMathConfig::SetInlineEditEnable( bool bVal ) +{ + if (!pOther) + LoadOther(); + if (SetOtherIfNotEqual( pOther->bInlineEditEnable, bVal )) + { + // reformat (displayed) formulas accordingly + Broadcast(SfxHint(SfxHintId::MathFormatChanged)); + } +} + +bool SmMathConfig::IsIgnoreSpacesRight() const +{ + if (utl::ConfigManager::IsFuzzing()) + return false; + if (!pOther) + const_cast<SmMathConfig*>(this)->LoadOther(); + return pOther->bIgnoreSpacesRight; +} + + +void SmMathConfig::SetIgnoreSpacesRight( bool bVal ) +{ + if (!pOther) + LoadOther(); + if (SetOtherIfNotEqual( pOther->bIgnoreSpacesRight, bVal )) + { + // reformat (displayed) formulas accordingly + Broadcast(SfxHint(SfxHintId::MathFormatChanged)); + } + +} + + +bool SmMathConfig::IsAutoRedraw() const +{ + if (!pOther) + const_cast<SmMathConfig*>(this)->LoadOther(); + return pOther->bAutoRedraw; +} + + +void SmMathConfig::SetAutoRedraw( bool bVal ) +{ + if (!pOther) + LoadOther(); + SetOtherIfNotEqual( pOther->bAutoRedraw, bVal ); +} + + +bool SmMathConfig::IsShowFormulaCursor() const +{ + if (!pOther) + const_cast<SmMathConfig*>(this)->LoadOther(); + return pOther->bFormulaCursor; +} + + +void SmMathConfig::SetShowFormulaCursor( bool bVal ) +{ + if (!pOther) + LoadOther(); + SetOtherIfNotEqual( pOther->bFormulaCursor, bVal ); +} + +void SmMathConfig::Notify( const css::uno::Sequence< OUString >& rNames ) +{ + Clear(); + if (std::find(rNames.begin(), rNames.end(), "Misc/IgnoreSpacesRight") != rNames.end()) + Broadcast(SfxHint(SfxHintId::MathFormatChanged)); +} + + +void SmMathConfig::ItemSetToConfig(const SfxItemSet &rSet) +{ + CommitLocker aLock(*this); + + sal_uInt16 nU16; + bool bVal; + if (const SfxUInt16Item* pPrintSizeItem = rSet.GetItemIfSet(SID_PRINTSIZE)) + { nU16 = pPrintSizeItem->GetValue(); + SetPrintSize( static_cast<SmPrintSize>(nU16) ); + } + if (const SfxUInt16Item* pPrintZoomItem = rSet.GetItemIfSet(SID_PRINTZOOM)) + { nU16 = pPrintZoomItem->GetValue(); + SetPrintZoomFactor( nU16 ); + } + if (const SfxUInt16Item* pPrintZoomItem = rSet.GetItemIfSet(SID_SMEDITWINDOWZOOM)) + { nU16 = pPrintZoomItem->GetValue(); + SetSmEditWindowZoomFactor( nU16 ); + } + if (const SfxBoolItem* pPrintTitleItem = rSet.GetItemIfSet(SID_PRINTTITLE)) + { bVal = pPrintTitleItem->GetValue(); + SetPrintTitle( bVal ); + } + if (const SfxBoolItem* pPrintTextItem = rSet.GetItemIfSet(SID_PRINTTEXT)) + { bVal = pPrintTextItem->GetValue(); + SetPrintFormulaText( bVal ); + } + if (const SfxBoolItem* pPrintZoomItem = rSet.GetItemIfSet(SID_PRINTFRAME)) + { bVal = pPrintZoomItem->GetValue(); + SetPrintFrame( bVal ); + } + if (const SfxBoolItem* pRedrawItem = rSet.GetItemIfSet(SID_AUTOREDRAW)) + { bVal = pRedrawItem->GetValue(); + SetAutoRedraw( bVal ); + } + if (const SfxBoolItem* pSpacesItem = rSet.GetItemIfSet(SID_INLINE_EDIT_ENABLE)) + { bVal = pSpacesItem->GetValue(); + SetInlineEditEnable( bVal ); + } + if (const SfxBoolItem* pSpacesItem = rSet.GetItemIfSet(SID_NO_RIGHT_SPACES)) + { bVal = pSpacesItem->GetValue(); + SetIgnoreSpacesRight( bVal ); + } + if (const SfxBoolItem* pSymbolsItem = rSet.GetItemIfSet(SID_SAVE_ONLY_USED_SYMBOLS)) + { bVal = pSymbolsItem->GetValue(); + SetSaveOnlyUsedSymbols( bVal ); + } + + if (const SfxBoolItem* pBracketsItem = rSet.GetItemIfSet(SID_AUTO_CLOSE_BRACKETS)) + { + bVal = pBracketsItem->GetValue(); + SetAutoCloseBrackets( bVal ); + } + + if (const SfxUInt16Item* pSyntaxItem = rSet.GetItemIfSet(SID_DEFAULT_SM_SYNTAX_VERSION)) + { + nU16 = pSyntaxItem->GetValue(); + SetDefaultSmSyntaxVersion( nU16 ); + } +} + + +void SmMathConfig::ConfigToItemSet(SfxItemSet &rSet) const +{ + rSet.Put(SfxUInt16Item(SID_PRINTSIZE, + sal::static_int_cast<sal_uInt16>(GetPrintSize()))); + rSet.Put(SfxUInt16Item(SID_PRINTZOOM, + GetPrintZoomFactor())); + rSet.Put(SfxUInt16Item(SID_SMEDITWINDOWZOOM, + GetSmEditWindowZoomFactor())); + + rSet.Put(SfxBoolItem(SID_PRINTTITLE, IsPrintTitle())); + rSet.Put(SfxBoolItem(SID_PRINTTEXT, IsPrintFormulaText())); + rSet.Put(SfxBoolItem(SID_PRINTFRAME, IsPrintFrame())); + rSet.Put(SfxBoolItem(SID_AUTOREDRAW, IsAutoRedraw())); + rSet.Put(SfxBoolItem(SID_INLINE_EDIT_ENABLE, IsInlineEditEnable())); + rSet.Put(SfxBoolItem(SID_NO_RIGHT_SPACES, IsIgnoreSpacesRight())); + rSet.Put(SfxBoolItem(SID_SAVE_ONLY_USED_SYMBOLS, IsSaveOnlyUsedSymbols())); + rSet.Put(SfxBoolItem(SID_AUTO_CLOSE_BRACKETS, IsAutoCloseBrackets())); + rSet.Put(SfxBoolItem(SID_DEFAULT_SM_SYNTAX_VERSION, GetDefaultSmSyntaxVersion())); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/cursor.cxx b/starmath/source/cursor.cxx new file mode 100644 index 0000000000..6f8e9b0af0 --- /dev/null +++ b/starmath/source/cursor.cxx @@ -0,0 +1,1606 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +#include <cursor.hxx> +#include <visitors.hxx> +#include <document.hxx> +#include <view.hxx> +#include <comphelper/string.hxx> +#include <comphelper/lok.hxx> +#include <editeng/editeng.hxx> +#include <LibreOfficeKit/LibreOfficeKitEnums.h> +#include <osl/diagnose.h> +#include <sfx2/lokhelper.hxx> +#include <vcl/transfer.hxx> +#include <vcl/unohelp2.hxx> + +void SmCursor::Move(OutputDevice* pDev, SmMovementDirection direction, bool bMoveAnchor){ + SmCaretPosGraphEntry* NewPos = nullptr; + switch(direction) + { + case MoveLeft: + if (mpPosition) + NewPos = mpPosition->Left; + OSL_ENSURE(NewPos, "NewPos shouldn't be NULL here!"); + break; + case MoveRight: + if (mpPosition) + NewPos = mpPosition->Right; + OSL_ENSURE(NewPos, "NewPos shouldn't be NULL here!"); + break; + case MoveUp: + //Implementation is practically identical to MoveDown, except for a single if statement + //so I've implemented them together and added a direction == MoveDown to the if statements. + case MoveDown: + if (mpPosition) + { + SmCaretLine from_line = SmCaretPos2LineVisitor(pDev, mpPosition->CaretPos).GetResult(), + best_line, //Best approximated line found so far + curr_line; //Current line + tools::Long dbp_sq = 0; //Distance squared to best line + for(const auto &pEntry : *mpGraph) + { + //Reject it if it's the current position + if(pEntry->CaretPos == mpPosition->CaretPos) continue; + //Compute caret line + curr_line = SmCaretPos2LineVisitor(pDev, pEntry->CaretPos).GetResult(); + //Reject anything above if we're moving down + if(curr_line.GetTop() <= from_line.GetTop() && direction == MoveDown) continue; + //Reject anything below if we're moving up + if(curr_line.GetTop() + curr_line.GetHeight() >= from_line.GetTop() + from_line.GetHeight() + && direction == MoveUp) continue; + //Compare if it to what we have, if we have anything yet + if(NewPos){ + //Compute distance to current line squared, multiplied with a horizontal factor + tools::Long dp_sq = curr_line.SquaredDistanceX(from_line) * HORIZONTICAL_DISTANCE_FACTOR + + curr_line.SquaredDistanceY(from_line); + //Discard current line if best line is closer + if(dbp_sq <= dp_sq) continue; + } + //Take current line as the best + best_line = curr_line; + NewPos = pEntry.get(); + //Update distance to best line + dbp_sq = best_line.SquaredDistanceX(from_line) * HORIZONTICAL_DISTANCE_FACTOR + + best_line.SquaredDistanceY(from_line); + } + } + break; + default: + assert(false); + } + if(NewPos){ + mpPosition = NewPos; + if(bMoveAnchor) + mpAnchor = NewPos; + RequestRepaint(); + } +} + +void SmCursor::MoveTo(OutputDevice* pDev, const Point& pos, bool bMoveAnchor) +{ + SmCaretPosGraphEntry* NewPos = nullptr; + tools::Long dp_sq = 0, //Distance to current line squared + dbp_sq = 1; //Distance to best line squared + for(const auto &pEntry : *mpGraph) + { + OSL_ENSURE(pEntry->CaretPos.IsValid(), "The caret position graph may not have invalid positions!"); + //Compute current line + SmCaretLine curr_line = SmCaretPos2LineVisitor(pDev, pEntry->CaretPos).GetResult(); + //Compute squared distance to current line + dp_sq = curr_line.SquaredDistanceX(pos) + curr_line.SquaredDistanceY(pos); + //If we have a position compare to it + if(NewPos){ + //If best line is closer, reject current line + if(dbp_sq <= dp_sq) continue; + } + //Accept current position as the best + NewPos = pEntry.get(); + //Update distance to best line + dbp_sq = dp_sq; + } + if(NewPos){ + mpPosition = NewPos; + if(bMoveAnchor) + mpAnchor = NewPos; + RequestRepaint(); + } +} + +void SmCursor::BuildGraph(){ + //Save the current anchor and position + SmCaretPos _anchor, _position; + //Release mpGraph if allocated + if(mpGraph){ + if(mpAnchor) + _anchor = mpAnchor->CaretPos; + if(mpPosition) + _position = mpPosition->CaretPos; + mpGraph.reset(); + //Reset anchor and position as they point into an old graph + mpAnchor = nullptr; + mpPosition = nullptr; + } + + //Build the new graph + mpGraph.reset(SmCaretPosGraphBuildingVisitor(mpTree).takeGraph()); + + //Restore anchor and position pointers + if(_anchor.IsValid() || _position.IsValid()){ + for(const auto &pEntry : *mpGraph) + { + if(_anchor == pEntry->CaretPos) + mpAnchor = pEntry.get(); + if(_position == pEntry->CaretPos) + mpPosition = pEntry.get(); + } + } + //Set position and anchor to first caret position + auto it = mpGraph->begin(); + assert(it != mpGraph->end()); + if(!mpPosition) + mpPosition = it->get(); + if(!mpAnchor) + mpAnchor = mpPosition; + + assert(mpPosition); + assert(mpAnchor); + OSL_ENSURE(mpPosition->CaretPos.IsValid(), "Position must be valid"); + OSL_ENSURE(mpAnchor->CaretPos.IsValid(), "Anchor must be valid"); +} + +bool SmCursor::SetCaretPosition(SmCaretPos pos){ + for(const auto &pEntry : *mpGraph) + { + if(pEntry->CaretPos == pos) + { + mpPosition = pEntry.get(); + mpAnchor = pEntry.get(); + return true; + } + } + return false; +} + +void SmCursor::AnnotateSelection() const { + //TODO: Manage a state, reset it upon modification and optimize this call + SmSetSelectionVisitor(mpAnchor->CaretPos, mpPosition->CaretPos, mpTree); +} + +void SmCursor::Draw(OutputDevice& pDev, Point Offset, bool isCaretVisible){ + SmCaretDrawingVisitor(pDev, GetPosition(), Offset, isCaretVisible); +} + +tools::Rectangle SmCursor::GetCaretRectangle(OutputDevice& rOutDev) const +{ + return SmCaretRectanglesVisitor(rOutDev, GetPosition()).getCaret(); +} + +tools::Rectangle SmCursor::GetSelectionRectangle(OutputDevice& rOutDev) const +{ + AnnotateSelection(); + return SmSelectionRectanglesVisitor(rOutDev, mpTree).GetSelection(); +} + +void SmCursor::DeletePrev(OutputDevice* pDev){ + //Delete only a selection if there's a selection + if(HasSelection()){ + Delete(); + return; + } + + SmNode* pLine = FindTopMostNodeInLine(mpPosition->CaretPos.pSelectedNode); + SmStructureNode* pLineParent = pLine->GetParent(); + int nLineOffsetIdx = pLineParent->IndexOfSubNode(pLine); + assert(nLineOffsetIdx >= 0); + + //If we're in front of a node who's parent is a TABLE + if (pLineParent->GetType() == SmNodeType::Table && mpPosition->CaretPos.nIndex == 0 && nLineOffsetIdx > 0) + { + size_t nLineOffset = nLineOffsetIdx; + //Now we can merge with nLineOffset - 1 + BeginEdit(); + //Line to merge things into, so we can delete pLine + SmNode* pMergeLine = pLineParent->GetSubNode(nLineOffset-1); + OSL_ENSURE(pMergeLine, "pMergeLine cannot be NULL!"); + SmCaretPos PosAfterDelete; + //Convert first line to list + std::unique_ptr<SmNodeList> pLineList(new SmNodeList); + NodeToList(pMergeLine, *pLineList); + if(!pLineList->empty()){ + //Find iterator to patch + SmNodeList::iterator patchPoint = pLineList->end(); + --patchPoint; + //Convert second line to list + NodeToList(pLine, *pLineList); + //Patch the line list + ++patchPoint; + PosAfterDelete = PatchLineList(pLineList.get(), patchPoint); + //Parse the line + pLine = SmNodeListParser().Parse(pLineList.get()); + } + pLineList.reset(); + pLineParent->SetSubNode(nLineOffset-1, pLine); + //Delete the removed line slot + SmNodeArray lines(pLineParent->GetNumSubNodes()-1); + for (size_t i = 0; i < pLineParent->GetNumSubNodes(); ++i) + { + if(i < nLineOffset) + lines[i] = pLineParent->GetSubNode(i); + else if(i > nLineOffset) + lines[i-1] = pLineParent->GetSubNode(i); + } + pLineParent->SetSubNodes(std::move(lines)); + //Rebuild graph + mpAnchor = nullptr; + mpPosition = nullptr; + BuildGraph(); + AnnotateSelection(); + //Set caret position + if(!SetCaretPosition(PosAfterDelete)) + SetCaretPosition(SmCaretPos(pLine, 0)); + //Finish editing + EndEdit(); + + //TODO: If we're in an empty (sub/super/*) script + /*}else if(pLineParent->GetType() == SmNodeType::SubSup && + nLineOffset != 0 && + pLine->GetType() == SmNodeType::Expression && + pLine->GetNumSubNodes() == 0){ + //There's a (sub/super) script we can delete + //Consider selecting the entire script if GetNumSubNodes() != 0 or pLine->GetType() != SmNodeType::Expression + //TODO: Handle case where we delete a limit + */ + + //Else move select, and delete if not complex + }else{ + Move(pDev, MoveLeft, false); + if(!HasComplexSelection()) + Delete(); + } +} + +void SmCursor::Delete(){ + //Return if we don't have a selection to delete + if(!HasSelection()) + return; + + //Enter edit section + BeginEdit(); + + //Set selected on nodes + AnnotateSelection(); + + //Find an arbitrary selected node + SmNode* pSNode = FindSelectedNode(mpTree); + assert(pSNode); + + //Find the topmost node of the line that holds the selection + SmNode* pLine = FindTopMostNodeInLine(pSNode, true); + OSL_ENSURE(pLine != mpTree, "Shouldn't be able to select the entire tree"); + + //Get the parent of the line + SmStructureNode* pLineParent = pLine->GetParent(); + //Find line offset in parent + int nLineOffset = pLineParent->IndexOfSubNode(pLine); + assert(nLineOffset >= 0); + + //Position after delete + SmCaretPos PosAfterDelete; + + std::unique_ptr<SmNodeList> pLineList(new SmNodeList); + NodeToList(pLine, *pLineList); + + //Take the selected nodes and delete them... + SmNodeList::iterator patchIt = TakeSelectedNodesFromList(pLineList.get()); + + //Get the position to set after delete + PosAfterDelete = PatchLineList(pLineList.get(), patchIt); + + //Finish editing + FinishEdit(std::move(pLineList), pLineParent, nLineOffset, PosAfterDelete); +} + +void SmCursor::InsertNodes(std::unique_ptr<SmNodeList> pNewNodes){ + if(pNewNodes->empty()){ + return; + } + + //Begin edit section + BeginEdit(); + + //Get the current position + const SmCaretPos pos = mpPosition->CaretPos; + + //Find top most of line that holds position + SmNode* pLine = FindTopMostNodeInLine(pos.pSelectedNode); + const bool bSelectedIsTopMost = pLine == pos.pSelectedNode; + + //Find line parent and line index in parent + SmStructureNode* pLineParent = pLine->GetParent(); + int nParentIndex = pLineParent->IndexOfSubNode(pLine); + assert(nParentIndex >= 0); + + //Convert line to list + std::unique_ptr<SmNodeList> pLineList(new SmNodeList); + NodeToList(pLine, *pLineList); // deletes pLine, potentially deleting pos.pSelectedNode + + //Find iterator for place to insert nodes + SmNodeList::iterator it = bSelectedIsTopMost ? pLineList->begin() + : FindPositionInLineList(pLineList.get(), pos); + + //Insert all new nodes + SmNodeList::iterator newIt, + patchIt = it, // (pointless default value, fixes compiler warnings) + insIt; + for(newIt = pNewNodes->begin(); newIt != pNewNodes->end(); ++newIt){ + insIt = pLineList->insert(it, *newIt); + if(newIt == pNewNodes->begin()) + patchIt = insIt; + } + //Patch the places we've changed stuff + PatchLineList(pLineList.get(), patchIt); + SmCaretPos PosAfterInsert = PatchLineList(pLineList.get(), it); + //Release list, we've taken the nodes + pNewNodes.reset(); + + //Finish editing + FinishEdit(std::move(pLineList), pLineParent, nParentIndex, PosAfterInsert); +} + +SmNodeList::iterator SmCursor::FindPositionInLineList(SmNodeList* pLineList, + const SmCaretPos& rCaretPos) +{ + //Find iterator for position + SmNodeList::iterator it = std::find(pLineList->begin(), pLineList->end(), rCaretPos.pSelectedNode); + if (it != pLineList->end()) + { + if((*it)->GetType() == SmNodeType::Text) + { + //Split textnode if needed + if(rCaretPos.nIndex > 0) + { + SmTextNode* pText = static_cast<SmTextNode*>(rCaretPos.pSelectedNode); + if (rCaretPos.nIndex == pText->GetText().getLength()) + return ++it; + OUString str1 = pText->GetText().copy(0, rCaretPos.nIndex); + OUString str2 = pText->GetText().copy(rCaretPos.nIndex); + pText->ChangeText(str1); + ++it; + //Insert str2 as new text node + assert(!str2.isEmpty()); + SmTextNode* pNewText = new SmTextNode(pText->GetToken(), pText->GetFontDesc()); + pNewText->ChangeText(str2); + it = pLineList->insert(it, pNewText); + } + }else + ++it; + //it now pointer to the node following pos, so pLineList->insert(it, ...) will insert correctly + return it; + } + //If we didn't find pSelectedNode, it must be because the caret is in front of the line + return pLineList->begin(); +} + +SmCaretPos SmCursor::PatchLineList(SmNodeList* pLineList, SmNodeList::iterator aIter) { + //The nodes we should consider merging + SmNode *prev = nullptr, + *next = nullptr; + if(aIter != pLineList->end()) + next = *aIter; + if(aIter != pLineList->begin()) { + --aIter; + prev = *aIter; + ++aIter; + } + + //Check if there's textnodes to merge + if( prev && + next && + prev->GetType() == SmNodeType::Text && + next->GetType() == SmNodeType::Text && + ( prev->GetToken().eType != TNUMBER || + next->GetToken().eType == TNUMBER) ){ + SmTextNode *pText = static_cast<SmTextNode*>(prev), + *pOldN = static_cast<SmTextNode*>(next); + SmCaretPos retval(pText, pText->GetText().getLength()); + OUString newText = pText->GetText() + pOldN->GetText(); + pText->ChangeText(newText); + delete pOldN; + pLineList->erase(aIter); + return retval; + } + + //Check if there's a SmPlaceNode to remove: + if(prev && next && prev->GetType() == SmNodeType::Place && !SmNodeListParser::IsOperator(next->GetToken())){ + --aIter; + aIter = pLineList->erase(aIter); + delete prev; + //Return caret pos in front of aIter + if(aIter != pLineList->begin()) + --aIter; //Thus find node before aIter + if(aIter == pLineList->begin()) + return SmCaretPos(); + return SmCaretPos::GetPosAfter(*aIter); + } + if(prev && next && next->GetType() == SmNodeType::Place && !SmNodeListParser::IsOperator(prev->GetToken())){ + aIter = pLineList->erase(aIter); + delete next; + return SmCaretPos::GetPosAfter(prev); + } + + //If we didn't do anything return + if(!prev) //return an invalid to indicate we're in front of line + return SmCaretPos(); + return SmCaretPos::GetPosAfter(prev); +} + +SmNodeList::iterator SmCursor::TakeSelectedNodesFromList(SmNodeList *pLineList, + SmNodeList *pSelectedNodes) { + SmNodeList::iterator retval; + SmNodeList::iterator it = pLineList->begin(); + while(it != pLineList->end()){ + if((*it)->IsSelected()){ + //Split text nodes + if((*it)->GetType() == SmNodeType::Text) { + SmTextNode* pText = static_cast<SmTextNode*>(*it); + OUString aText = pText->GetText(); + //Start and lengths of the segments, 2 is the selected segment + int start2 = pText->GetSelectionStart(), + start3 = pText->GetSelectionEnd(), + len1 = start2 - 0, + len2 = start3 - start2, + len3 = aText.getLength() - start3; + SmToken aToken = pText->GetToken(); + sal_uInt16 eFontDesc = pText->GetFontDesc(); + //If we need make segment 1 + if(len1 > 0) { + OUString str = aText.copy(0, len1); + pText->ChangeText(str); + ++it; + } else {//Remove it if not needed + it = pLineList->erase(it); + delete pText; + } + //Set retval to be right after the selection + retval = it; + //if we need make segment 3 + if(len3 > 0) { + OUString str = aText.copy(start3, len3); + SmTextNode* pSeg3 = new SmTextNode(aToken, eFontDesc); + pSeg3->ChangeText(str); + retval = pLineList->insert(it, pSeg3); + } + //If we need to save the selected text + if(pSelectedNodes && len2 > 0) { + OUString str = aText.copy(start2, len2); + SmTextNode* pSeg2 = new SmTextNode(aToken, eFontDesc); + pSeg2->ChangeText(str); + pSelectedNodes->push_back(pSeg2); + } + } else { //if it's not textnode + SmNode* pNode = *it; + retval = it = pLineList->erase(it); + if(pSelectedNodes) + pSelectedNodes->push_back(pNode); + else + delete pNode; + } + } else + ++it; + } + return retval; +} + +void SmCursor::InsertSubSup(SmSubSup eSubSup) { + AnnotateSelection(); + + //Find line + SmNode *pLine; + if(HasSelection()) { + SmNode *pSNode = FindSelectedNode(mpTree); + assert(pSNode); + pLine = FindTopMostNodeInLine(pSNode, true); + } else + pLine = FindTopMostNodeInLine(mpPosition->CaretPos.pSelectedNode); + + //Find Parent and offset in parent + SmStructureNode *pLineParent = pLine->GetParent(); + int nParentIndex = pLineParent->IndexOfSubNode(pLine); + assert(nParentIndex >= 0); + + //TODO: Consider handling special cases where parent is an SmOperNode, + // Maybe this method should be able to add limits to an SmOperNode... + + //We begin modifying the tree here + BeginEdit(); + + //Convert line to list + std::unique_ptr<SmNodeList> pLineList(new SmNodeList); + NodeToList(pLine, *pLineList); + + //Take the selection, and/or find iterator for current position + std::unique_ptr<SmNodeList> pSelectedNodesList(new SmNodeList); + SmNodeList::iterator it; + if(HasSelection()) + it = TakeSelectedNodesFromList(pLineList.get(), pSelectedNodesList.get()); + else + it = FindPositionInLineList(pLineList.get(), mpPosition->CaretPos); + + //Find node that this should be applied to + SmNode* pSubject; + bool bPatchLine = !pSelectedNodesList->empty(); //If the line should be patched later + if(it != pLineList->begin()) { + --it; + pSubject = *it; + ++it; + } else { + //Create a new place node + pSubject = new SmPlaceNode(); + pSubject->Prepare(mpDocShell->GetFormat(), *mpDocShell, 0); + it = pLineList->insert(it, pSubject); + ++it; + bPatchLine = true; //We've modified the line it should be patched later. + } + + //Wrap the subject in a SmSubSupNode + SmSubSupNode* pSubSup; + if(pSubject->GetType() != SmNodeType::SubSup){ + SmToken token; + token.nGroup = TG::Power; + pSubSup = new SmSubSupNode(token); + pSubSup->SetBody(pSubject); + *(--it) = pSubSup; + ++it; + }else + pSubSup = static_cast<SmSubSupNode*>(pSubject); + //pSubject shouldn't be referenced anymore, pSubSup is the SmSubSupNode in pLineList we wish to edit. + //and it pointer to the element following pSubSup in pLineList. + pSubject = nullptr; + + //Patch the line if we noted that was needed previously + if(bPatchLine) + PatchLineList(pLineList.get(), it); + + //Convert existing, if any, sub-/superscript line to list + SmNode *pScriptLine = pSubSup->GetSubSup(eSubSup); + std::unique_ptr<SmNodeList> pScriptLineList(new SmNodeList); + NodeToList(pScriptLine, *pScriptLineList); + + //Add selection to pScriptLineList + unsigned int nOldSize = pScriptLineList->size(); + pScriptLineList->insert(pScriptLineList->end(), pSelectedNodesList->begin(), pSelectedNodesList->end()); + pSelectedNodesList.reset(); + + //Patch pScriptLineList if needed + if(0 < nOldSize && nOldSize < pScriptLineList->size()) { + SmNodeList::iterator iPatchPoint = pScriptLineList->begin(); + std::advance(iPatchPoint, nOldSize); + PatchLineList(pScriptLineList.get(), iPatchPoint); + } + + //Find caret pos, that should be used after sub-/superscription. + SmCaretPos PosAfterScript; //Leave invalid for first position + if (!pScriptLineList->empty()) + PosAfterScript = SmCaretPos::GetPosAfter(pScriptLineList->back()); + + //Parse pScriptLineList + pScriptLine = SmNodeListParser().Parse(pScriptLineList.get()); + pScriptLineList.reset(); + + //Insert pScriptLine back into the tree + pSubSup->SetSubSup(eSubSup, pScriptLine); + + //Finish editing + FinishEdit(std::move(pLineList), pLineParent, nParentIndex, PosAfterScript, pScriptLine); +} + +void SmCursor::InsertBrackets(SmBracketType eBracketType) { + BeginEdit(); + + AnnotateSelection(); + + //Find line + SmNode *pLine; + if(HasSelection()) { + SmNode *pSNode = FindSelectedNode(mpTree); + assert(pSNode); + pLine = FindTopMostNodeInLine(pSNode, true); + } else + pLine = FindTopMostNodeInLine(mpPosition->CaretPos.pSelectedNode); + + //Find parent and offset in parent + SmStructureNode *pLineParent = pLine->GetParent(); + int nParentIndex = pLineParent->IndexOfSubNode(pLine); + assert(nParentIndex >= 0); + + //Convert line to list + std::unique_ptr<SmNodeList> pLineList(new SmNodeList); + NodeToList(pLine, *pLineList); + + //Take the selection, and/or find iterator for current position + std::unique_ptr<SmNodeList> pSelectedNodesList(new SmNodeList); + SmNodeList::iterator it; + if(HasSelection()) + it = TakeSelectedNodesFromList(pLineList.get(), pSelectedNodesList.get()); + else + it = FindPositionInLineList(pLineList.get(), mpPosition->CaretPos); + + //If there's no selected nodes, create a place node + std::unique_ptr<SmNode> pBodyNode; + SmCaretPos PosAfterInsert; + if(pSelectedNodesList->empty()) { + pBodyNode.reset(new SmPlaceNode()); + PosAfterInsert = SmCaretPos(pBodyNode.get(), 1); + } else + pBodyNode.reset(SmNodeListParser().Parse(pSelectedNodesList.get())); + + pSelectedNodesList.reset(); + + //Create SmBraceNode + SmToken aTok(TLEFT, '\0', "left", TG::NONE, 5); + SmBraceNode *pBrace = new SmBraceNode(aTok); + pBrace->SetScaleMode(SmScaleMode::Height); + std::unique_ptr<SmNode> pLeft( CreateBracket(eBracketType, true) ), + pRight( CreateBracket(eBracketType, false) ); + std::unique_ptr<SmBracebodyNode> pBody(new SmBracebodyNode(SmToken())); + pBody->SetSubNodes(std::move(pBodyNode), nullptr); + pBrace->SetSubNodes(std::move(pLeft), std::move(pBody), std::move(pRight)); + pBrace->Prepare(mpDocShell->GetFormat(), *mpDocShell, 0); + + //Insert into line + pLineList->insert(it, pBrace); + //Patch line (I think this is good enough) + SmCaretPos aAfter = PatchLineList(pLineList.get(), it); + if( !PosAfterInsert.IsValid() ) + PosAfterInsert = aAfter; + + //Finish editing + FinishEdit(std::move(pLineList), pLineParent, nParentIndex, PosAfterInsert); +} + +SmNode *SmCursor::CreateBracket(SmBracketType eBracketType, bool bIsLeft) { + SmToken aTok; + if(bIsLeft){ + switch(eBracketType){ + case SmBracketType::Round: + aTok = SmToken(TLPARENT, MS_LPARENT, "(", TG::LBrace, 5); + break; + case SmBracketType::Square: + aTok = SmToken(TLBRACKET, MS_LBRACKET, "[", TG::LBrace, 5); + break; + case SmBracketType::Curly: + aTok = SmToken(TLBRACE, MS_LBRACE, "lbrace", TG::LBrace, 5); + break; + } + } else { + switch(eBracketType) { + case SmBracketType::Round: + aTok = SmToken(TRPARENT, MS_RPARENT, ")", TG::RBrace, 5); + break; + case SmBracketType::Square: + aTok = SmToken(TRBRACKET, MS_RBRACKET, "]", TG::RBrace, 5); + break; + case SmBracketType::Curly: + aTok = SmToken(TRBRACE, MS_RBRACE, "rbrace", TG::RBrace, 5); + break; + } + } + SmNode* pRetVal = new SmMathSymbolNode(aTok); + pRetVal->SetScaleMode(SmScaleMode::Height); + return pRetVal; +} + +bool SmCursor::InsertRow() { + AnnotateSelection(); + + //Find line + SmNode *pLine; + if(HasSelection()) { + SmNode *pSNode = FindSelectedNode(mpTree); + assert(pSNode); + pLine = FindTopMostNodeInLine(pSNode, true); + } else + pLine = FindTopMostNodeInLine(mpPosition->CaretPos.pSelectedNode); + + //Find parent and offset in parent + SmStructureNode *pLineParent = pLine->GetParent(); + int nParentIndex = pLineParent->IndexOfSubNode(pLine); + assert(nParentIndex >= 0); + + //Discover the context of this command + SmTableNode *pTable = nullptr; + SmMatrixNode *pMatrix = nullptr; + int nTableIndex = nParentIndex; + if(pLineParent->GetType() == SmNodeType::Table) + pTable = static_cast<SmTableNode*>(pLineParent); + //If it's wrapped in a SmLineNode, we can still insert a newline + else if(pLineParent->GetType() == SmNodeType::Line && + pLineParent->GetParent() && + pLineParent->GetParent()->GetType() == SmNodeType::Table) { + //NOTE: This hack might give problems if we stop ignoring SmAlignNode + pTable = static_cast<SmTableNode*>(pLineParent->GetParent()); + nTableIndex = pTable->IndexOfSubNode(pLineParent); + assert(nTableIndex >= 0); + } + if(pLineParent->GetType() == SmNodeType::Matrix) + pMatrix = static_cast<SmMatrixNode*>(pLineParent); + + //If we're not in a context that supports InsertRow, return sal_False + if(!pTable && !pMatrix) + return false; + + //Now we start editing + BeginEdit(); + + //Convert line to list + std::unique_ptr<SmNodeList> pLineList(new SmNodeList); + NodeToList(pLine, *pLineList); + + //Find position in line + SmNodeList::iterator it; + if(HasSelection()) { + //Take the selected nodes and delete them... + it = TakeSelectedNodesFromList(pLineList.get()); + } else + it = FindPositionInLineList(pLineList.get(), mpPosition->CaretPos); + + //New caret position after inserting the newline/row in whatever context + SmCaretPos PosAfterInsert; + + //If we're in the context of a table + if(pTable) { + std::unique_ptr<SmNodeList> pNewLineList(new SmNodeList); + //Move elements from pLineList to pNewLineList + SmNodeList& rLineList = *pLineList; + pNewLineList->splice(pNewLineList->begin(), rLineList, it, rLineList.end()); + //Make sure it is valid again + it = pLineList->end(); + if(it != pLineList->begin()) + --it; + if(pNewLineList->empty()) + pNewLineList->push_front(new SmPlaceNode()); + //Parse new line + std::unique_ptr<SmNode> pNewLine(SmNodeListParser().Parse(pNewLineList.get())); + pNewLineList.reset(); + //Wrap pNewLine in SmLineNode if needed + if(pLineParent->GetType() == SmNodeType::Line) { + std::unique_ptr<SmLineNode> pNewLineNode(new SmLineNode(SmToken(TNEWLINE, '\0', "newline"))); + pNewLineNode->SetSubNodes(std::move(pNewLine), nullptr); + pNewLine = std::move(pNewLineNode); + } + //Get position + PosAfterInsert = SmCaretPos(pNewLine.get(), 0); + //Move other nodes if needed + for( int i = pTable->GetNumSubNodes(); i > nTableIndex + 1; i--) + pTable->SetSubNode(i, pTable->GetSubNode(i-1)); + + //Insert new line + pTable->SetSubNode(nTableIndex + 1, pNewLine.release()); + + //Check if we need to change token type: + if(pTable->GetNumSubNodes() > 2 && pTable->GetToken().eType == TBINOM) { + SmToken tok = pTable->GetToken(); + tok.eType = TSTACK; + pTable->SetToken(tok); + } + } + //If we're in the context of a matrix + else { + //Find position after insert and patch the list + PosAfterInsert = PatchLineList(pLineList.get(), it); + //Move other children + sal_uInt16 rows = pMatrix->GetNumRows(); + sal_uInt16 cols = pMatrix->GetNumCols(); + int nRowStart = (nParentIndex - nParentIndex % cols) + cols; + for( int i = pMatrix->GetNumSubNodes() + cols - 1; i >= nRowStart + cols; i--) + pMatrix->SetSubNode(i, pMatrix->GetSubNode(i - cols)); + for( int i = nRowStart; i < nRowStart + cols; i++) { + SmPlaceNode *pNewLine = new SmPlaceNode(); + if(i == nParentIndex + cols) + PosAfterInsert = SmCaretPos(pNewLine, 0); + pMatrix->SetSubNode(i, pNewLine); + } + pMatrix->SetRowCol(rows + 1, cols); + } + + //Finish editing + FinishEdit(std::move(pLineList), pLineParent, nParentIndex, PosAfterInsert); + //FinishEdit is actually used to handle situations where parent is an instance of + //SmSubSupNode. In this case parent should always be a table or matrix, however, for + //code reuse we just use FinishEdit() here too. + return true; +} + +void SmCursor::InsertFraction() { + AnnotateSelection(); + + //Find line + SmNode *pLine; + if(HasSelection()) { + SmNode *pSNode = FindSelectedNode(mpTree); + assert(pSNode); + pLine = FindTopMostNodeInLine(pSNode, true); + } else + pLine = FindTopMostNodeInLine(mpPosition->CaretPos.pSelectedNode); + + //Find Parent and offset in parent + SmStructureNode *pLineParent = pLine->GetParent(); + int nParentIndex = pLineParent->IndexOfSubNode(pLine); + assert(nParentIndex >= 0); + + //We begin modifying the tree here + BeginEdit(); + + //Convert line to list + std::unique_ptr<SmNodeList> pLineList(new SmNodeList); + NodeToList(pLine, *pLineList); + + //Take the selection, and/or find iterator for current position + std::unique_ptr<SmNodeList> pSelectedNodesList(new SmNodeList); + SmNodeList::iterator it; + if(HasSelection()) + it = TakeSelectedNodesFromList(pLineList.get(), pSelectedNodesList.get()); + else + it = FindPositionInLineList(pLineList.get(), mpPosition->CaretPos); + + //Create pNum, and pDenom + bool bEmptyFraction = pSelectedNodesList->empty(); + std::unique_ptr<SmNode> pNum( bEmptyFraction + ? new SmPlaceNode() + : SmNodeListParser().Parse(pSelectedNodesList.get()) ); + std::unique_ptr<SmNode> pDenom(new SmPlaceNode()); + pSelectedNodesList.reset(); + + //Create new fraction + SmBinVerNode *pFrac = new SmBinVerNode(SmToken(TOVER, '\0', "over", TG::Product, 0)); + std::unique_ptr<SmNode> pRect(new SmRectangleNode(SmToken())); + pFrac->SetSubNodes(std::move(pNum), std::move(pRect), std::move(pDenom)); + + //Insert in pLineList + SmNodeList::iterator patchIt = pLineList->insert(it, pFrac); + PatchLineList(pLineList.get(), patchIt); + PatchLineList(pLineList.get(), it); + + //Finish editing + SmNode *pSelectedNode = bEmptyFraction ? pFrac->GetSubNode(0) : pFrac->GetSubNode(2); + FinishEdit(std::move(pLineList), pLineParent, nParentIndex, SmCaretPos(pSelectedNode, 1)); +} + +void SmCursor::InsertText(const OUString& aString) +{ + BeginEdit(); + + Delete(); + + SmToken token; + token.eType = TIDENT; + token.cMathChar = u""_ustr; + token.nGroup = TG::NONE; + token.nLevel = 5; + token.aText = aString; + + SmTextNode* pText = new SmTextNode(token, FNT_VARIABLE); + pText->SetText(aString); + pText->AdjustFontDesc(); + pText->Prepare(mpDocShell->GetFormat(), *mpDocShell, 0); + + std::unique_ptr<SmNodeList> pList(new SmNodeList); + pList->push_front(pText); + InsertNodes(std::move(pList)); + + EndEdit(); +} + +void SmCursor::InsertElement(SmFormulaElement element){ + BeginEdit(); + + Delete(); + + //Create new node + SmNode* pNewNode = nullptr; + switch(element){ + case BlankElement: + { + SmToken token; + token.eType = TBLANK; + token.nGroup = TG::Blank; + token.aText = "~"; + SmBlankNode* pBlankNode = new SmBlankNode(token); + pBlankNode->IncreaseBy(token); + pNewNode = pBlankNode; + }break; + case FactorialElement: + { + SmToken token(TFACT, MS_FACT, "fact", TG::UnOper, 5); + pNewNode = new SmMathSymbolNode(token); + }break; + case PlusElement: + { + SmToken token; + token.eType = TPLUS; + token.setChar(MS_PLUS); + token.nGroup = TG::UnOper | TG::Sum; + token.nLevel = 5; + token.aText = "+"; + pNewNode = new SmMathSymbolNode(token); + }break; + case MinusElement: + { + SmToken token; + token.eType = TMINUS; + token.setChar(MS_MINUS); + token.nGroup = TG::UnOper | TG::Sum; + token.nLevel = 5; + token.aText = "-"; + pNewNode = new SmMathSymbolNode(token); + }break; + case CDotElement: + { + SmToken token; + token.eType = TCDOT; + token.setChar(MS_CDOT); + token.nGroup = TG::Product; + token.aText = "cdot"; + pNewNode = new SmMathSymbolNode(token); + }break; + case EqualElement: + { + SmToken token; + token.eType = TASSIGN; + token.setChar(MS_ASSIGN); + token.nGroup = TG::Relation; + token.aText = "="; + pNewNode = new SmMathSymbolNode(token); + }break; + case LessThanElement: + { + SmToken token; + token.eType = TLT; + token.setChar(MS_LT); + token.nGroup = TG::Relation; + token.aText = "<"; + pNewNode = new SmMathSymbolNode(token); + }break; + case GreaterThanElement: + { + SmToken token; + token.eType = TGT; + token.setChar(MS_GT); + token.nGroup = TG::Relation; + token.aText = ">"; + pNewNode = new SmMathSymbolNode(token); + }break; + case PercentElement: + { + SmToken token; + token.eType = TTEXT; + token.setChar(MS_PERCENT); + token.nGroup = TG::NONE; + token.aText = "\"%\""; + pNewNode = new SmMathSymbolNode(token); + }break; + } + assert(pNewNode); + + //Prepare the new node + pNewNode->Prepare(mpDocShell->GetFormat(), *mpDocShell, 0); + + //Insert new node + std::unique_ptr<SmNodeList> pList(new SmNodeList); + pList->push_front(pNewNode); + InsertNodes(std::move(pList)); + + EndEdit(); +} + +void SmCursor::InsertSpecial(std::u16string_view _aString) +{ + BeginEdit(); + Delete(); + + OUString aString( comphelper::string::strip(_aString, ' ') ); + + //Create instance of special node + SmToken token; + token.eType = TSPECIAL; + token.cMathChar = u""_ustr; + token.nGroup = TG::NONE; + token.nLevel = 5; + token.aText = aString; + SmSpecialNode* pSpecial = new SmSpecialNode(token); + + //Prepare the special node + pSpecial->Prepare(mpDocShell->GetFormat(), *mpDocShell, 0); + + //Insert the node + std::unique_ptr<SmNodeList> pList(new SmNodeList); + pList->push_front(pSpecial); + InsertNodes(std::move(pList)); + + EndEdit(); +} + +void SmCursor::InsertCommandText(const OUString& aCommandText) { + //Parse the sub expression + auto xSubExpr = mpDocShell->GetParser()->ParseExpression(aCommandText); + + //Prepare the subtree + xSubExpr->Prepare(mpDocShell->GetFormat(), *mpDocShell, 0); + + //Convert subtree to list + SmNode* pSubExpr = xSubExpr.release(); + std::unique_ptr<SmNodeList> pLineList(new SmNodeList); + NodeToList(pSubExpr, *pLineList); + + BeginEdit(); + + //Delete any selection + Delete(); + + //Insert it + InsertNodes(std::move(pLineList)); + + EndEdit(); +} + +void SmCursor::Copy(vcl::Window* pWindow) +{ + if(!HasSelection()) + return; + + AnnotateSelection(); + //Find selected node + SmNode* pSNode = FindSelectedNode(mpTree); + assert(pSNode); + //Find visual line + SmNode* pLine = FindTopMostNodeInLine(pSNode, true); + assert(pLine); + + //Clone selected nodes + // TODO: Simplify all this cloning since we only need a formula string now. + SmClipboard aClipboard; + if(IsLineCompositionNode(pLine)) + CloneLineToClipboard(static_cast<SmStructureNode*>(pLine), &aClipboard); + else{ + //Special care to only clone selected text + if(pLine->GetType() == SmNodeType::Text) { + SmTextNode *pText = static_cast<SmTextNode*>(pLine); + std::unique_ptr<SmTextNode> pClone(new SmTextNode( pText->GetToken(), pText->GetFontDesc() )); + int start = pText->GetSelectionStart(), + length = pText->GetSelectionEnd() - pText->GetSelectionStart(); + pClone->ChangeText(pText->GetText().copy(start, length)); + pClone->SetScaleMode(pText->GetScaleMode()); + aClipboard.push_front(std::move(pClone)); + } else { + SmCloningVisitor aCloneFactory; + aClipboard.push_front(std::unique_ptr<SmNode>(aCloneFactory.Clone(pLine))); + } + } + + // Parse list of nodes to a tree + SmNodeListParser parser; + SmNode* pTree(parser.Parse(CloneList(aClipboard).get())); + + // Parse the tree to a string + OUString aString; + SmNodeToTextVisitor(pTree, aString); + + //Set clipboard + auto xClipboard(pWindow ? pWindow->GetClipboard() : GetSystemClipboard()); + vcl::unohelper::TextDataObject::CopyStringTo(aString, xClipboard); +} + +void SmCursor::Paste(vcl::Window* pWindow) +{ + BeginEdit(); + Delete(); + + auto xClipboard(pWindow ? pWindow->GetClipboard() : GetSystemClipboard()); + auto aDataHelper(TransferableDataHelper::CreateFromClipboard(xClipboard)); + if (aDataHelper.GetTransferable().is()) + { + // TODO: Support MATHML + auto nId = SotClipboardFormatId::STRING; + if (aDataHelper.HasFormat(nId)) + { + OUString aString; + if (aDataHelper.GetString(nId, aString)) + InsertCommandText(aString); + } + } + + EndEdit(); +} + +std::unique_ptr<SmNodeList> SmCursor::CloneList(SmClipboard &rClipboard){ + SmCloningVisitor aCloneFactory; + std::unique_ptr<SmNodeList> pClones(new SmNodeList); + + for(auto &xNode : rClipboard){ + SmNode *pClone = aCloneFactory.Clone(xNode.get()); + pClones->push_back(pClone); + } + + return pClones; +} + +SmNode* SmCursor::FindTopMostNodeInLine(SmNode* pSNode, bool MoveUpIfSelected){ + assert(pSNode); + //Move up parent until we find a node who's + //parent is NULL or isn't selected and not a type of: + // SmExpressionNode + // SmLineNode + // SmBinHorNode + // SmUnHorNode + // SmAlignNode + // SmFontNode + while(pSNode->GetParent() && + ((MoveUpIfSelected && + pSNode->GetParent()->IsSelected()) || + IsLineCompositionNode(pSNode->GetParent()))) + pSNode = pSNode->GetParent(); + //Now we have the selection line node + return pSNode; +} + +SmNode* SmCursor::FindSelectedNode(SmNode* pNode){ + if(pNode->GetNumSubNodes() == 0) + return nullptr; + for(auto pChild : *static_cast<SmStructureNode*>(pNode)) + { + if(!pChild) + continue; + if(pChild->IsSelected()) + return pChild; + SmNode* pRetVal = FindSelectedNode(pChild); + if(pRetVal) + return pRetVal; + } + return nullptr; +} + +void SmCursor::LineToList(SmStructureNode* pLine, SmNodeList& list){ + for(auto pChild : *pLine) + { + if (!pChild) + continue; + switch(pChild->GetType()){ + case SmNodeType::Line: + case SmNodeType::UnHor: + case SmNodeType::Expression: + case SmNodeType::BinHor: + case SmNodeType::Align: + case SmNodeType::Font: + LineToList(static_cast<SmStructureNode*>(pChild), list); + break; + case SmNodeType::Error: + delete pChild; + break; + default: + list.push_back(pChild); + } + } + pLine->ClearSubNodes(); + delete pLine; +} + +void SmCursor::CloneLineToClipboard(SmStructureNode* pLine, SmClipboard* pClipboard){ + SmCloningVisitor aCloneFactory; + for(auto pChild : *pLine) + { + if (!pChild) + continue; + if( IsLineCompositionNode( pChild ) ) + CloneLineToClipboard( static_cast<SmStructureNode*>(pChild), pClipboard ); + else if( pChild->IsSelected() && pChild->GetType() != SmNodeType::Error ) { + //Only clone selected text from SmTextNode + if(pChild->GetType() == SmNodeType::Text) { + SmTextNode *pText = static_cast<SmTextNode*>(pChild); + std::unique_ptr<SmTextNode> pClone(new SmTextNode( pChild->GetToken(), pText->GetFontDesc() )); + int start = pText->GetSelectionStart(), + length = pText->GetSelectionEnd() - pText->GetSelectionStart(); + pClone->ChangeText(pText->GetText().copy(start, length)); + pClone->SetScaleMode(pText->GetScaleMode()); + pClipboard->push_back(std::move(pClone)); + } else + pClipboard->push_back(std::unique_ptr<SmNode>(aCloneFactory.Clone(pChild))); + } + } +} + +bool SmCursor::IsLineCompositionNode(SmNode const * pNode){ + switch(pNode->GetType()){ + case SmNodeType::Line: + case SmNodeType::UnHor: + case SmNodeType::Expression: + case SmNodeType::BinHor: + case SmNodeType::Align: + case SmNodeType::Font: + return true; + default: + return false; + } +} + +int SmCursor::CountSelectedNodes(SmNode* pNode){ + if(pNode->GetNumSubNodes() == 0) + return 0; + int nCount = 0; + for(auto pChild : *static_cast<SmStructureNode*>(pNode)) + { + if (!pChild) + continue; + if(pChild->IsSelected() && !IsLineCompositionNode(pChild)) + nCount++; + nCount += CountSelectedNodes(pChild); + } + return nCount; +} + +bool SmCursor::HasComplexSelection(){ + if(!HasSelection()) + return false; + AnnotateSelection(); + + return CountSelectedNodes(mpTree) > 1; +} + +void SmCursor::FinishEdit(std::unique_ptr<SmNodeList> pLineList, + SmStructureNode* pParent, + int nParentIndex, + SmCaretPos PosAfterEdit, + SmNode* pStartLine) { + //Store number of nodes in line for later + int entries = pLineList->size(); + + //Parse list of nodes to a tree + SmNodeListParser parser; + std::unique_ptr<SmNode> pLine(parser.Parse(pLineList.get())); + pLineList.reset(); + + //Check if we're making the body of a subsup node bigger than one + if(pParent->GetType() == SmNodeType::SubSup && + nParentIndex == 0 && + entries > 1) { + //Wrap pLine in scalable round brackets + SmToken aTok(TLEFT, '\0', "left", TG::NONE, 5); + std::unique_ptr<SmBraceNode> pBrace(new SmBraceNode(aTok)); + pBrace->SetScaleMode(SmScaleMode::Height); + std::unique_ptr<SmNode> pLeft( CreateBracket(SmBracketType::Round, true) ), + pRight( CreateBracket(SmBracketType::Round, false) ); + std::unique_ptr<SmBracebodyNode> pBody(new SmBracebodyNode(SmToken())); + pBody->SetSubNodes(std::move(pLine), nullptr); + pBrace->SetSubNodes(std::move(pLeft), std::move(pBody), std::move(pRight)); + pBrace->Prepare(mpDocShell->GetFormat(), *mpDocShell, 0); + pLine = std::move(pBrace); + //TODO: Consider the following alternative behavior: + //Consider the line: A + {B + C}^D lsub E + //Here pLineList is B, + and C and pParent is a subsup node with + //both RSUP and LSUB set. Imagine the user just inserted "B +" in + //the body of the subsup node... + //The most natural thing to do would be to make the line like this: + //A + B lsub E + C ^ D + //E.g. apply LSUB and LSUP to the first element in pLineList and RSUP + //and RSUB to the last element in pLineList. But how should this act + //for CSUP and CSUB ??? + //For this reason and because brackets was faster to implement, this solution + //have been chosen. It might be worth working on the other solution later... + } + + //Set pStartLine if NULL + if(!pStartLine) + pStartLine = pLine.get(); + + //Insert it back into the parent + pParent->SetSubNode(nParentIndex, pLine.release()); + + //Rebuild graph of caret position + mpAnchor = nullptr; + mpPosition = nullptr; + BuildGraph(); + AnnotateSelection(); //Update selection annotation! + + //Set caret position + if(!SetCaretPosition(PosAfterEdit)) + SetCaretPosition(SmCaretPos(pStartLine, 0)); + + //End edit section + EndEdit(); +} + +void SmCursor::BeginEdit(){ + if(mnEditSections++ > 0) return; + + mbIsEnabledSetModifiedSmDocShell = mpDocShell->IsEnableSetModified(); + if( mbIsEnabledSetModifiedSmDocShell ) + mpDocShell->EnableSetModified( false ); +} + +void SmCursor::EndEdit(){ + if(--mnEditSections > 0) return; + + mpDocShell->SetFormulaArranged(false); + //Okay, I don't know what this does... :) + //It's used in SmDocShell::SetText and with places where everything is modified. + //I think it does some magic, with sfx, but everything is totally undocumented so + //it's kinda hard to tell... + if ( mbIsEnabledSetModifiedSmDocShell ) + mpDocShell->EnableSetModified( mbIsEnabledSetModifiedSmDocShell ); + //I think this notifies people around us that we've modified this document... + mpDocShell->SetModified(); + //I think SmDocShell uses this value when it sends an update graphics event + //Anyway comments elsewhere suggests it needs to be updated... + mpDocShell->mnModifyCount++; + + //TODO: Consider copying the update accessibility code from SmDocShell::SetText in here... + //This somehow updates the size of SmGraphicView if it is running in embedded mode + if( mpDocShell->GetCreateMode() == SfxObjectCreateMode::EMBEDDED ) + mpDocShell->OnDocumentPrinterChanged(nullptr); + + //Request a repaint... + RequestRepaint(); + + //Update the edit engine and text of the document + OUString formula; + SmNodeToTextVisitor(mpTree, formula); + mpDocShell->maText = formula; + mpDocShell->GetEditEngine().QuickInsertText( formula, ESelection( 0, 0, EE_PARA_ALL, EE_TEXTPOS_ALL ) ); + mpDocShell->GetEditEngine().QuickFormatDoc(); +} + +void SmCursor::RequestRepaint() +{ + if (SmViewShell *pViewSh = SmGetActiveView()) + { + if (comphelper::LibreOfficeKit::isActive()) + { + pViewSh->SendCaretToLOK(); + } + else if ( SfxObjectCreateMode::EMBEDDED == mpDocShell->GetCreateMode() ) + mpDocShell->Repaint(); + else + pViewSh->GetGraphicWidget().Invalidate(); + } +} + +bool SmCursor::IsAtTailOfBracket(SmBracketType eBracketType) const +{ + const SmCaretPos pos = GetPosition(); + if (!pos.IsValid()) { + return false; + } + + SmNode* pNode = pos.pSelectedNode; + + if (pNode->GetType() == SmNodeType::Text) { + SmTextNode* pTextNode = static_cast<SmTextNode*>(pNode); + if (pos.nIndex < pTextNode->GetText().getLength()) { + // The cursor is on a text node and at the middle of it. + return false; + } + } else { + if (pos.nIndex < 1) { + return false; + } + } + + while (true) { + SmStructureNode* pParentNode = pNode->GetParent(); + if (!pParentNode) { + // There's no brace body node in the ancestors. + return false; + } + + int index = pParentNode->IndexOfSubNode(pNode); + assert(index >= 0); + if (static_cast<size_t>(index + 1) != pParentNode->GetNumSubNodes()) { + // The cursor is not at the tail at one of ancestor nodes. + return false; + } + + pNode = pParentNode; + if (pNode->GetType() == SmNodeType::Bracebody) { + // Found the brace body node. + break; + } + } + + SmStructureNode* pBraceNodeTmp = pNode->GetParent(); + if (!pBraceNodeTmp || pBraceNodeTmp->GetType() != SmNodeType::Brace) { + // Brace node is invalid. + return false; + } + + SmBraceNode* pBraceNode = static_cast<SmBraceNode*>(pBraceNodeTmp); + SmMathSymbolNode* pClosingNode = pBraceNode->ClosingBrace(); + if (!pClosingNode) { + // Couldn't get closing symbol node. + return false; + } + + // Check if the closing brace matches eBracketType. + SmTokenType eClosingTokenType = pClosingNode->GetToken().eType; + switch (eBracketType) { + case SmBracketType::Round: if (eClosingTokenType != TRPARENT) { return false; } break; + case SmBracketType::Square: if (eClosingTokenType != TRBRACKET) { return false; } break; + case SmBracketType::Curly: if (eClosingTokenType != TRBRACE) { return false; } break; + default: + return false; + } + + return true; +} + +/////////////////////////////////////// SmNodeListParser + +SmNode* SmNodeListParser::Parse(SmNodeList* list){ + pList = list; + //Delete error nodes + SmNodeList::iterator it = pList->begin(); + while(it != pList->end()) { + if((*it)->GetType() == SmNodeType::Error){ + //Delete and erase + delete *it; + it = pList->erase(it); + }else + ++it; + } + SmNode* retval = Expression(); + pList = nullptr; + return retval; +} + +SmNode* SmNodeListParser::Expression(){ + SmNodeArray NodeArray; + //Accept as many relations as there is + while(Terminal()) + NodeArray.push_back(Relation()); + + //Create SmExpressionNode, I hope SmToken() will do :) + SmStructureNode* pExpr = new SmExpressionNode(SmToken()); + pExpr->SetSubNodes(std::move(NodeArray)); + return pExpr; +} + +SmNode* SmNodeListParser::Relation(){ + //Read a sum + std::unique_ptr<SmNode> pLeft(Sum()); + //While we have tokens and the next is a relation + while(Terminal() && IsRelationOperator(Terminal()->GetToken())){ + //Take the operator + std::unique_ptr<SmNode> pOper(Take()); + //Find the right side of the relation + std::unique_ptr<SmNode> pRight(Sum()); + //Create new SmBinHorNode + std::unique_ptr<SmStructureNode> pNewNode(new SmBinHorNode(SmToken())); + pNewNode->SetSubNodes(std::move(pLeft), std::move(pOper), std::move(pRight)); + pLeft = std::move(pNewNode); + } + return pLeft.release(); +} + +SmNode* SmNodeListParser::Sum(){ + //Read a product + std::unique_ptr<SmNode> pLeft(Product()); + //While we have tokens and the next is a sum + while(Terminal() && IsSumOperator(Terminal()->GetToken())){ + //Take the operator + std::unique_ptr<SmNode> pOper(Take()); + //Find the right side of the sum + std::unique_ptr<SmNode> pRight(Product()); + //Create new SmBinHorNode + std::unique_ptr<SmStructureNode> pNewNode(new SmBinHorNode(SmToken())); + pNewNode->SetSubNodes(std::move(pLeft), std::move(pOper), std::move(pRight)); + pLeft = std::move(pNewNode); + } + return pLeft.release(); +} + +SmNode* SmNodeListParser::Product(){ + //Read a Factor + std::unique_ptr<SmNode> pLeft(Factor()); + //While we have tokens and the next is a product + while(Terminal() && IsProductOperator(Terminal()->GetToken())){ + //Take the operator + std::unique_ptr<SmNode> pOper(Take()); + //Find the right side of the operation + std::unique_ptr<SmNode> pRight(Factor()); + //Create new SmBinHorNode + std::unique_ptr<SmStructureNode> pNewNode(new SmBinHorNode(SmToken())); + pNewNode->SetSubNodes(std::move(pLeft), std::move(pOper), std::move(pRight)); + pLeft = std::move(pNewNode); + } + return pLeft.release(); +} + +SmNode* SmNodeListParser::Factor(){ + //Read unary operations + if(!Terminal()) + return Error(); + //Take care of unary operators + else if(IsUnaryOperator(Terminal()->GetToken())) + { + SmStructureNode *pUnary = new SmUnHorNode(SmToken()); + std::unique_ptr<SmNode> pOper(Terminal()), + pArg; + + if(Next()) + pArg.reset(Factor()); + else + pArg.reset(Error()); + + pUnary->SetSubNodes(std::move(pOper), std::move(pArg)); + return pUnary; + } + return Postfix(); +} + +SmNode* SmNodeListParser::Postfix(){ + if(!Terminal()) + return Error(); + std::unique_ptr<SmNode> pArg; + if(IsPostfixOperator(Terminal()->GetToken())) + pArg.reset(Error()); + else if(IsOperator(Terminal()->GetToken())) + return Error(); + else + pArg.reset(Take()); + while(Terminal() && IsPostfixOperator(Terminal()->GetToken())) { + std::unique_ptr<SmStructureNode> pUnary(new SmUnHorNode(SmToken()) ); + std::unique_ptr<SmNode> pOper(Take()); + pUnary->SetSubNodes(std::move(pArg), std::move(pOper)); + pArg = std::move(pUnary); + } + return pArg.release(); +} + +SmNode* SmNodeListParser::Error(){ + return new SmErrorNode(SmToken()); +} + +bool SmNodeListParser::IsOperator(const SmToken &token) { + return IsRelationOperator(token) || + IsSumOperator(token) || + IsProductOperator(token) || + IsUnaryOperator(token) || + IsPostfixOperator(token); +} + +bool SmNodeListParser::IsRelationOperator(const SmToken &token) { + return bool(token.nGroup & TG::Relation); +} + +bool SmNodeListParser::IsSumOperator(const SmToken &token) { + return bool(token.nGroup & TG::Sum); +} + +bool SmNodeListParser::IsProductOperator(const SmToken &token) { + return token.nGroup & TG::Product && + token.eType != TWIDESLASH && + token.eType != TWIDEBACKSLASH && + token.eType != TUNDERBRACE && + token.eType != TOVERBRACE && + token.eType != TOVER; +} + +bool SmNodeListParser::IsUnaryOperator(const SmToken &token) { + return token.nGroup & TG::UnOper && + (token.eType == TPLUS || + token.eType == TMINUS || + token.eType == TPLUSMINUS || + token.eType == TMINUSPLUS || + token.eType == TNEG || + token.eType == TUOPER); +} + +bool SmNodeListParser::IsPostfixOperator(const SmToken &token) { + return token.eType == TFACT; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/dialog.cxx b/starmath/source/dialog.cxx new file mode 100644 index 0000000000..1ad8376e5a --- /dev/null +++ b/starmath/source/dialog.cxx @@ -0,0 +1,2170 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <sal/log.hxx> + +#include <comphelper/string.hxx> +#include <o3tl/temporary.hxx> +#include <svl/eitem.hxx> +#include <svl/intitem.hxx> +#include <svl/stritem.hxx> +#include <vcl/event.hxx> +#include <vcl/svapp.hxx> +#include <vcl/virdev.hxx> +#include <vcl/weld.hxx> +#include <svtools/ctrltool.hxx> +#include <vcl/settings.hxx> +#include <vcl/wall.hxx> +#include <vcl/fontcharmap.hxx> +#include <sfx2/dispatch.hxx> +#include <svx/charmap.hxx> +#include <svx/ucsubset.hxx> + +#include <dialog.hxx> +#include <starmath.hrc> +#include <strings.hrc> +#include <helpids.h> +#include <cfgitem.hxx> +#include <officecfg/Office/Math.hxx> +#include <smmod.hxx> +#include <symbol.hxx> +#include <view.hxx> + +#include <algorithm> + +namespace +{ + +void lclGetSettingColors(Color& rBackgroundColor, Color& rTextColor) +{ + const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); + if (rStyleSettings.GetHighContrastMode()) + { + rBackgroundColor = rStyleSettings.GetFieldColor(); + rTextColor = rStyleSettings.GetFieldTextColor(); + } + else + { + rBackgroundColor = rStyleSettings.GetFaceColor(); + rTextColor = rStyleSettings.GetLabelTextColor(); + } +} + +// Since it's better to set/query the FontStyle via its attributes rather +// than via the StyleName we create a way to translate +// Attribute <-> StyleName + +class SmFontStyles +{ + OUString aNormal; + OUString aBold; + OUString aItalic; + OUString aBoldItalic; + +public: + SmFontStyles(); + + static sal_uInt16 GetCount() { return 4; } + const OUString& GetStyleName(const vcl::Font& rFont) const; + const OUString& GetStyleName(sal_uInt16 nIdx) const; +}; + +vcl::Font lclGetSymbolFont(const SmViewShell& rViewShell, const SmSym &rSymbol) +{ + const SmDocShell* pDoc = rViewShell.GetDoc(); + if (pDoc) + { + // If we have a document, we want to render the symbol using the font and style used in + // the document, so we do that by creating a node and preparing it, then get the resolved + // font and style from it. + SmToken token(TSPECIAL, '\0', "%" + rSymbol.GetUiName()); + SmSpecialNode aNode(token); + aNode.Prepare(pDoc->GetFormat(), *pDoc, 1); + aNode.PrepareAttributes(); + return aNode.GetFont(); + } + + return rSymbol.GetFace(); +} + +} // end anonymous namespace + +SmFontStyles::SmFontStyles() + : aNormal(SmResId(RID_FONTREGULAR)) + , aBold(SmResId(RID_FONTBOLD)) + , aItalic(SmResId(RID_FONTITALIC)) +{ + aBoldItalic = aBold; + aBoldItalic += ", "; + aBoldItalic += aItalic; +} + +const OUString& SmFontStyles::GetStyleName(const vcl::Font& rFont) const +{ + //! compare also SmSpecialNode::Prepare + bool bBold = IsBold( rFont ), + bItalic = IsItalic( rFont ); + + if (bBold && bItalic) + return aBoldItalic; + else if (bItalic) + return aItalic; + else if (bBold) + return aBold; + return aNormal; +} + +const OUString& SmFontStyles::GetStyleName( sal_uInt16 nIdx ) const +{ + // 0 = "normal", 1 = "italic", + // 2 = "bold", 3 = "bold italic" + + assert( nIdx < GetCount() ); + switch (nIdx) + { + case 0 : return aNormal; + case 1 : return aItalic; + case 2 : return aBold; + default: /*case 3:*/ return aBoldItalic; + } +} + +static const SmFontStyles & GetFontStyles() +{ + static const SmFontStyles aImpl; + return aImpl; +} + +void SetFontStyle(std::u16string_view rStyleName, vcl::Font &rFont) +{ + // Find index related to StyleName. For an empty StyleName it's assumed to be + // 0 (neither bold nor italic). + sal_uInt16 nIndex = 0; + if (!rStyleName.empty()) + { + sal_uInt16 i; + const SmFontStyles &rStyles = GetFontStyles(); + for (i = 0; i < SmFontStyles::GetCount(); ++i) + if (rStyleName == rStyles.GetStyleName(i)) + break; + assert(i < SmFontStyles::GetCount() && "style-name unknown"); + nIndex = i; + } + + rFont.SetItalic((nIndex & 0x1) ? ITALIC_NORMAL : ITALIC_NONE); + rFont.SetWeight((nIndex & 0x2) ? WEIGHT_BOLD : WEIGHT_NORMAL); +} + +IMPL_LINK_NOARG(SmPrintOptionsTabPage, SizeButtonClickHdl, weld::Toggleable&, void) +{ + m_xZoom->set_sensitive(m_xSizeZoomed->get_active()); +} + +SmPrintOptionsTabPage::SmPrintOptionsTabPage(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet& rOptions) + : SfxTabPage(pPage, pController, "modules/smath/ui/smathsettings.ui", "SmathSettings", &rOptions) + , m_xTitle(m_xBuilder->weld_check_button("title")) + , m_xTitleImg(m_xBuilder->weld_widget("locktitle")) + , m_xText(m_xBuilder->weld_check_button("text")) + , m_xTextImg(m_xBuilder->weld_widget("locktext")) + , m_xFrame(m_xBuilder->weld_check_button("frame")) + , m_xFrameImg(m_xBuilder->weld_widget("lockframe")) + , m_xSizeNormal(m_xBuilder->weld_radio_button("sizenormal")) + , m_xSizeScaled(m_xBuilder->weld_radio_button("sizescaled")) + , m_xSizeZoomed(m_xBuilder->weld_radio_button("sizezoomed")) + , m_xLockPrintImg(m_xBuilder->weld_widget("lockprintformat")) + , m_xZoom(m_xBuilder->weld_metric_spin_button("zoom", FieldUnit::PERCENT)) + , m_xEnableInlineEdit(m_xBuilder->weld_check_button("enableinlineedit")) + , m_xEnableInlineEditImg(m_xBuilder->weld_widget("lockenableinlineedit")) + , m_xNoRightSpaces(m_xBuilder->weld_check_button("norightspaces")) + , m_xNoRightSpacesImg(m_xBuilder->weld_widget("locknorightspaces")) + , m_xSaveOnlyUsedSymbols(m_xBuilder->weld_check_button("saveonlyusedsymbols")) + , m_xSaveOnlyUsedSymbolsImg(m_xBuilder->weld_widget("locksaveonlyusedsymbols")) + , m_xAutoCloseBrackets(m_xBuilder->weld_check_button("autoclosebrackets")) + , m_xAutoCloseBracketsImg(m_xBuilder->weld_widget("lockautoclosebrackets")) + , m_xSmZoom(m_xBuilder->weld_metric_spin_button("smzoom", FieldUnit::PERCENT)) + , m_xSmZoomImg(m_xBuilder->weld_widget("locksmzoom")) +{ + m_xSizeNormal->connect_toggled(LINK(this, SmPrintOptionsTabPage, SizeButtonClickHdl)); + m_xSizeScaled->connect_toggled(LINK(this, SmPrintOptionsTabPage, SizeButtonClickHdl)); + m_xSizeZoomed->connect_toggled(LINK(this, SmPrintOptionsTabPage, SizeButtonClickHdl)); + + Reset(&rOptions); +} + +SmPrintOptionsTabPage::~SmPrintOptionsTabPage() +{ + if (SmViewShell *pViewSh = SmGetActiveView()) + if (SmEditWindow* pEdit = pViewSh->GetEditWindow()) + pEdit->UpdateStatus(); +} + +OUString SmPrintOptionsTabPage::GetAllStrings() +{ + OUString sAllStrings; + OUString labels[] = { "label4", "label5", "label1", "label6" }; + + for (const auto& label : labels) + { + if (const auto& pString = m_xBuilder->weld_label(label)) + sAllStrings += pString->get_label() + " "; + } + + OUString checkButton[] + = { "title", "text", "frame", "norightspaces", "saveonlyusedsymbols", "autoclosebrackets" }; + + for (const auto& check : checkButton) + { + if (const auto& pString = m_xBuilder->weld_check_button(check)) + sAllStrings += pString->get_label() + " "; + } + + OUString radioButton[] = { "sizenormal", "sizescaled", "sizezoomed" }; + + for (const auto& radio : radioButton) + { + if (const auto& pString = m_xBuilder->weld_radio_button(radio)) + sAllStrings += pString->get_label() + " "; + } + + return sAllStrings.replaceAll("_", ""); +} + +bool SmPrintOptionsTabPage::FillItemSet(SfxItemSet* rSet) +{ + sal_uInt16 nPrintSize; + if (m_xSizeNormal->get_active()) + nPrintSize = PRINT_SIZE_NORMAL; + else if (m_xSizeScaled->get_active()) + nPrintSize = PRINT_SIZE_SCALED; + else + nPrintSize = PRINT_SIZE_ZOOMED; + + rSet->Put(SfxUInt16Item(SID_PRINTSIZE, nPrintSize)); + rSet->Put(SfxUInt16Item(SID_PRINTZOOM, sal::static_int_cast<sal_uInt16>(m_xZoom->get_value(FieldUnit::PERCENT)))); + rSet->Put(SfxBoolItem(SID_PRINTTITLE, m_xTitle->get_active())); + rSet->Put(SfxBoolItem(SID_PRINTTEXT, m_xText->get_active())); + rSet->Put(SfxBoolItem(SID_PRINTFRAME, m_xFrame->get_active())); + rSet->Put(SfxBoolItem(SID_INLINE_EDIT_ENABLE, m_xEnableInlineEdit->get_active())); + rSet->Put(SfxBoolItem(SID_NO_RIGHT_SPACES, m_xNoRightSpaces->get_active())); + rSet->Put(SfxBoolItem(SID_SAVE_ONLY_USED_SYMBOLS, m_xSaveOnlyUsedSymbols->get_active())); + rSet->Put(SfxBoolItem(SID_AUTO_CLOSE_BRACKETS, m_xAutoCloseBrackets->get_active())); + rSet->Put(SfxUInt16Item(SID_SMEDITWINDOWZOOM, sal::static_int_cast<sal_uInt16>(m_xSmZoom->get_value(FieldUnit::PERCENT)))); + + if (SmViewShell *pViewSh = SmGetActiveView()) + if (SmEditWindow* pEdit = pViewSh->GetEditWindow()) + pEdit->UpdateStatus(); + + return true; +} + +void SmPrintOptionsTabPage::Reset(const SfxItemSet* rSet) +{ + SmPrintSize ePrintSize = static_cast<SmPrintSize>(rSet->Get(SID_PRINTSIZE).GetValue()); + + m_xSizeNormal->set_active(ePrintSize == PRINT_SIZE_NORMAL); + m_xSizeScaled->set_active(ePrintSize == PRINT_SIZE_SCALED); + m_xSizeZoomed->set_active(ePrintSize == PRINT_SIZE_ZOOMED); + bool bReadOnly = officecfg::Office::Math::Print::Size::isReadOnly(); + if (bReadOnly) + { + m_xSizeNormal->set_sensitive(false); + m_xSizeScaled->set_sensitive(false); + m_xSizeZoomed->set_sensitive(false); + m_xLockPrintImg->set_visible(true); + } + + bReadOnly = officecfg::Office::Math::Print::ZoomFactor::isReadOnly(); + m_xZoom->set_value(rSet->Get(SID_PRINTZOOM).GetValue(), FieldUnit::PERCENT); + m_xZoom->set_sensitive(m_xSizeZoomed->get_active() && !bReadOnly); + + bReadOnly = officecfg::Office::Math::Misc::SmEditWindowZoomFactor::isReadOnly(); + m_xSmZoom->set_value(rSet->Get(SID_SMEDITWINDOWZOOM).GetValue(), FieldUnit::PERCENT); + m_xSmZoom->set_sensitive(!bReadOnly); + m_xSmZoomImg->set_visible(bReadOnly); + + bReadOnly = officecfg::Office::Math::Print::Title::isReadOnly(); + m_xTitle->set_active(rSet->Get(SID_PRINTTITLE).GetValue()); + m_xTitle->set_sensitive(!bReadOnly); + m_xTitleImg->set_visible(bReadOnly); + + bReadOnly = officecfg::Office::Math::Print::FormulaText::isReadOnly(); + m_xText->set_active(rSet->Get(GetWhich(SID_PRINTTEXT)).GetValue()); + m_xText->set_sensitive(!bReadOnly); + m_xTextImg->set_visible(bReadOnly); + + bReadOnly = officecfg::Office::Math::Print::Frame::isReadOnly(); + m_xFrame->set_active(rSet->Get(GetWhich(SID_PRINTFRAME)).GetValue()); + m_xFrame->set_sensitive(!bReadOnly); + m_xFrameImg->set_visible(bReadOnly); + + bReadOnly = officecfg::Office::Math::Misc::InlineEditEnable::isReadOnly(); + m_xEnableInlineEdit->set_active(rSet->Get(SID_INLINE_EDIT_ENABLE).GetValue()); + m_xEnableInlineEdit->set_sensitive(!bReadOnly); + m_xEnableInlineEditImg->set_visible(bReadOnly); + + bReadOnly = officecfg::Office::Math::Misc::IgnoreSpacesRight::isReadOnly(); + m_xNoRightSpaces->set_active(rSet->Get(SID_NO_RIGHT_SPACES).GetValue()); + m_xNoRightSpaces->set_sensitive(!bReadOnly); + m_xNoRightSpacesImg->set_visible(bReadOnly); + + bReadOnly = officecfg::Office::Math::LoadSave::IsSaveOnlyUsedSymbols::isReadOnly(); + m_xSaveOnlyUsedSymbols->set_active(rSet->Get(SID_SAVE_ONLY_USED_SYMBOLS).GetValue()); + m_xSaveOnlyUsedSymbols->set_sensitive(!bReadOnly); + m_xSaveOnlyUsedSymbolsImg->set_visible(bReadOnly); + + bReadOnly = officecfg::Office::Math::Misc::AutoCloseBrackets::isReadOnly(); + m_xAutoCloseBrackets->set_active(rSet->Get(SID_AUTO_CLOSE_BRACKETS).GetValue()); + m_xAutoCloseBrackets->set_sensitive(!bReadOnly); + m_xAutoCloseBracketsImg->set_visible(bReadOnly); +} + +std::unique_ptr<SfxTabPage> SmPrintOptionsTabPage::Create(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet& rSet) +{ + return std::make_unique<SmPrintOptionsTabPage>(pPage, pController, rSet); +} + +void SmShowFont::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& /*rRect*/) +{ + Color aBackColor; + Color aTextColor; + lclGetSettingColors(aBackColor, aTextColor); + + rRenderContext.SetBackground(Wallpaper(aBackColor)); + + vcl::Font aFont(maFont); + aFont.SetFontSize(Size(0, 24 * rRenderContext.GetDPIScaleFactor())); + aFont.SetAlignment(ALIGN_TOP); + rRenderContext.SetFont(aFont); + rRenderContext.SetTextColor(aTextColor); + + OUString sText(rRenderContext.GetFont().GetFamilyName()); + Size aTextSize(rRenderContext.GetTextWidth(sText), rRenderContext.GetTextHeight()); + + rRenderContext.DrawText(Point((rRenderContext.GetOutputSize().Width() - aTextSize.Width()) / 2, + (rRenderContext.GetOutputSize().Height() - aTextSize.Height()) / 2), sText); +} + +void SmShowFont::SetDrawingArea(weld::DrawingArea* pDrawingArea) +{ + CustomWidgetController::SetDrawingArea(pDrawingArea); + Size aSize(pDrawingArea->get_ref_device().LogicToPixel(Size(111 , 31), MapMode(MapUnit::MapAppFont))); + pDrawingArea->set_size_request(aSize.Width(), aSize.Height()); +} + +void SmShowFont::SetFont(const vcl::Font& rFont) +{ + maFont = rFont; + Invalidate(); +} + +IMPL_LINK( SmFontDialog, FontSelectHdl, weld::ComboBox&, rComboBox, void ) +{ + maFont.SetFamilyName(rComboBox.get_active_text()); + m_aShowFont.SetFont(maFont); +} + +IMPL_LINK_NOARG(SmFontDialog, AttrChangeHdl, weld::Toggleable&, void) +{ + if (m_xBoldCheckBox->get_active()) + maFont.SetWeight(WEIGHT_BOLD); + else + maFont.SetWeight(WEIGHT_NORMAL); + + if (m_xItalicCheckBox->get_active()) + maFont.SetItalic(ITALIC_NORMAL); + else + maFont.SetItalic(ITALIC_NONE); + + m_aShowFont.SetFont(maFont); +} + +void SmFontDialog::SetFont(const vcl::Font &rFont) +{ + maFont = rFont; + + m_xFontBox->set_active_text(maFont.GetFamilyName()); + m_xBoldCheckBox->set_active(IsBold(maFont)); + m_xItalicCheckBox->set_active(IsItalic(maFont)); + m_aShowFont.SetFont(maFont); +} + +SmFontDialog::SmFontDialog(weld::Window * pParent, OutputDevice *pFntListDevice, bool bHideCheckboxes) + : GenericDialogController(pParent, "modules/smath/ui/fontdialog.ui", "FontDialog") + , m_xFontBox(m_xBuilder->weld_entry_tree_view("fontgrid", "font", "fonts")) + , m_xAttrFrame(m_xBuilder->weld_widget("attrframe")) + , m_xBoldCheckBox(m_xBuilder->weld_check_button("bold")) + , m_xItalicCheckBox(m_xBuilder->weld_check_button("italic")) + , m_xShowFont(new weld::CustomWeld(*m_xBuilder, "preview", m_aShowFont)) +{ + m_xFontBox->set_height_request_by_rows(8); + + { + weld::WaitObject aWait(pParent); + + FontList aFontList( pFntListDevice ); + + sal_uInt16 nCount = aFontList.GetFontNameCount(); + for (sal_uInt16 i = 0; i < nCount; ++i) + { + m_xFontBox->append_text(aFontList.GetFontName(i).GetFamilyName()); + } + maFont.SetFontSize(Size(0, 24)); + maFont.SetWeight(WEIGHT_NORMAL); + maFont.SetItalic(ITALIC_NONE); + maFont.SetFamily(FAMILY_DONTKNOW); + maFont.SetPitch(PITCH_DONTKNOW); + maFont.SetCharSet(RTL_TEXTENCODING_DONTKNOW); + maFont.SetTransparent(true); + } + + m_xFontBox->connect_changed(LINK(this, SmFontDialog, FontSelectHdl)); + m_xBoldCheckBox->connect_toggled(LINK(this, SmFontDialog, AttrChangeHdl)); + m_xItalicCheckBox->connect_toggled(LINK(this, SmFontDialog, AttrChangeHdl)); + + if (bHideCheckboxes) + { + m_xBoldCheckBox->set_active(false); + m_xBoldCheckBox->set_sensitive(false); + m_xItalicCheckBox->set_active(false); + m_xItalicCheckBox->set_sensitive(false); + m_xAttrFrame->hide(); + } +} + +SmFontDialog::~SmFontDialog() +{ +} + +namespace { + +class SaveDefaultsQuery : public weld::MessageDialogController +{ +public: + explicit SaveDefaultsQuery(weld::Widget* pParent) + : MessageDialogController(pParent, "modules/smath/ui/savedefaultsdialog.ui", + "SaveDefaultsDialog") + { + } +}; + +} + +IMPL_LINK_NOARG( SmFontSizeDialog, DefaultButtonClickHdl, weld::Button&, void ) +{ + SaveDefaultsQuery aQuery(m_xDialog.get()); + if (aQuery.run() == RET_YES) + { + SmModule *pp = SM_MOD(); + SmFormat aFmt( pp->GetConfig()->GetStandardFormat() ); + WriteTo( aFmt ); + pp->GetConfig()->SetStandardFormat( aFmt ); + } +} + +SmFontSizeDialog::SmFontSizeDialog(weld::Window* pParent) + : GenericDialogController(pParent, "modules/smath/ui/fontsizedialog.ui", "FontSizeDialog") + , m_xBaseSize(m_xBuilder->weld_metric_spin_button("spinB_baseSize", FieldUnit::POINT)) + , m_xTextSize(m_xBuilder->weld_metric_spin_button("spinB_text", FieldUnit::PERCENT)) + , m_xIndexSize(m_xBuilder->weld_metric_spin_button("spinB_index", FieldUnit::PERCENT)) + , m_xFunctionSize(m_xBuilder->weld_metric_spin_button("spinB_function", FieldUnit::PERCENT)) + , m_xOperatorSize(m_xBuilder->weld_metric_spin_button("spinB_operator", FieldUnit::PERCENT)) + , m_xBorderSize(m_xBuilder->weld_metric_spin_button("spinB_limit", FieldUnit::PERCENT)) + , m_xDefaultButton(m_xBuilder->weld_button("default")) +{ + m_xDefaultButton->connect_clicked(LINK(this, SmFontSizeDialog, DefaultButtonClickHdl)); +} + +SmFontSizeDialog::~SmFontSizeDialog() +{ +} + +void SmFontSizeDialog::ReadFrom(const SmFormat &rFormat) +{ + //! watch out: round properly! + m_xBaseSize->set_value( + o3tl::convert(rFormat.GetBaseSize().Height(), SmO3tlLengthUnit(), o3tl::Length::pt), + FieldUnit::NONE); + + m_xTextSize->set_value( rFormat.GetRelSize(SIZ_TEXT), FieldUnit::NONE ); + m_xIndexSize->set_value( rFormat.GetRelSize(SIZ_INDEX), FieldUnit::NONE ); + m_xFunctionSize->set_value( rFormat.GetRelSize(SIZ_FUNCTION), FieldUnit::NONE ); + m_xOperatorSize->set_value( rFormat.GetRelSize(SIZ_OPERATOR), FieldUnit::NONE ); + m_xBorderSize->set_value( rFormat.GetRelSize(SIZ_LIMITS), FieldUnit::NONE ); +} + +void SmFontSizeDialog::WriteTo(SmFormat &rFormat) const +{ + rFormat.SetBaseSize( Size(0, o3tl::convert(m_xBaseSize->get_value(FieldUnit::NONE), o3tl::Length::pt, SmO3tlLengthUnit())) ); + + rFormat.SetRelSize(SIZ_TEXT, sal::static_int_cast<sal_uInt16>(m_xTextSize->get_value(FieldUnit::NONE))); + rFormat.SetRelSize(SIZ_INDEX, sal::static_int_cast<sal_uInt16>(m_xIndexSize->get_value(FieldUnit::NONE))); + rFormat.SetRelSize(SIZ_FUNCTION, sal::static_int_cast<sal_uInt16>(m_xFunctionSize->get_value(FieldUnit::NONE))); + rFormat.SetRelSize(SIZ_OPERATOR, sal::static_int_cast<sal_uInt16>(m_xOperatorSize->get_value(FieldUnit::NONE))); + rFormat.SetRelSize(SIZ_LIMITS, sal::static_int_cast<sal_uInt16>(m_xBorderSize->get_value(FieldUnit::NONE))); + + const Size aTmp (rFormat.GetBaseSize()); + for (sal_uInt16 i = FNT_BEGIN; i <= FNT_END; i++) + rFormat.SetFontSize(i, aTmp); + + rFormat.RequestApplyChanges(); +} + +IMPL_LINK(SmFontTypeDialog, MenuSelectHdl, const OUString&, rIdent, void) +{ + SmFontPickListBox *pActiveListBox; + + bool bHideCheckboxes = false; + if (rIdent == "math") + pActiveListBox = m_xMathFont.get(); + else if (rIdent == "variables") + pActiveListBox = m_xVariableFont.get(); + else if (rIdent == "functions") + pActiveListBox = m_xFunctionFont.get(); + else if (rIdent == "numbers") + pActiveListBox = m_xNumberFont.get(); + else if (rIdent == "text") + pActiveListBox = m_xTextFont.get(); + else if (rIdent == "serif") + { + pActiveListBox = m_xSerifFont.get(); + bHideCheckboxes = true; + } + else if (rIdent == "sansserif") + { + pActiveListBox = m_xSansFont.get(); + bHideCheckboxes = true; + } + else if (rIdent == "fixedwidth") + { + pActiveListBox = m_xFixedFont.get(); + bHideCheckboxes = true; + } + else + pActiveListBox = nullptr; + + if (pActiveListBox) + { + SmFontDialog aFontDialog(m_xDialog.get(), pFontListDev, bHideCheckboxes); + + pActiveListBox->WriteTo(aFontDialog); + if (aFontDialog.run() == RET_OK) + pActiveListBox->ReadFrom(aFontDialog); + } +} + +IMPL_LINK_NOARG(SmFontTypeDialog, DefaultButtonClickHdl, weld::Button&, void) +{ + SaveDefaultsQuery aQuery(m_xDialog.get()); + if (aQuery.run() == RET_YES) + { + SmModule *pp = SM_MOD(); + SmFormat aFmt( pp->GetConfig()->GetStandardFormat() ); + WriteTo( aFmt ); + pp->GetConfig()->SetStandardFormat( aFmt, true ); + } +} + +SmFontTypeDialog::SmFontTypeDialog(weld::Window* pParent, OutputDevice *pFntListDevice) + : GenericDialogController(pParent, "modules/smath/ui/fonttypedialog.ui", "FontsDialog") + , pFontListDev(pFntListDevice) + , m_xMathFont(new SmFontPickListBox(m_xBuilder->weld_combo_box("mathCB"))) + , m_xVariableFont(new SmFontPickListBox(m_xBuilder->weld_combo_box("variableCB"))) + , m_xFunctionFont(new SmFontPickListBox(m_xBuilder->weld_combo_box("functionCB"))) + , m_xNumberFont(new SmFontPickListBox(m_xBuilder->weld_combo_box("numberCB"))) + , m_xTextFont(new SmFontPickListBox(m_xBuilder->weld_combo_box("textCB"))) + , m_xSerifFont(new SmFontPickListBox(m_xBuilder->weld_combo_box("serifCB"))) + , m_xSansFont(new SmFontPickListBox(m_xBuilder->weld_combo_box("sansCB"))) + , m_xFixedFont(new SmFontPickListBox(m_xBuilder->weld_combo_box("fixedCB"))) + , m_xMenuButton(m_xBuilder->weld_menu_button("modify")) + , m_xDefaultButton(m_xBuilder->weld_button("default")) +{ + m_xDefaultButton->connect_clicked(LINK(this, SmFontTypeDialog, DefaultButtonClickHdl)); + m_xMenuButton->connect_selected(LINK(this, SmFontTypeDialog, MenuSelectHdl)); +} + +SmFontTypeDialog::~SmFontTypeDialog() +{ +} + +void SmFontTypeDialog::ReadFrom(const SmFormat &rFormat) +{ + SmModule *pp = SM_MOD(); + + *m_xMathFont = pp->GetConfig()->GetFontPickList(FNT_MATH); + *m_xVariableFont = pp->GetConfig()->GetFontPickList(FNT_VARIABLE); + *m_xFunctionFont = pp->GetConfig()->GetFontPickList(FNT_FUNCTION); + *m_xNumberFont = pp->GetConfig()->GetFontPickList(FNT_NUMBER); + *m_xTextFont = pp->GetConfig()->GetFontPickList(FNT_TEXT); + *m_xSerifFont = pp->GetConfig()->GetFontPickList(FNT_SERIF); + *m_xSansFont = pp->GetConfig()->GetFontPickList(FNT_SANS); + *m_xFixedFont = pp->GetConfig()->GetFontPickList(FNT_FIXED); + + m_xMathFont->Insert( rFormat.GetFont(FNT_MATH) ); + m_xVariableFont->Insert( rFormat.GetFont(FNT_VARIABLE) ); + m_xFunctionFont->Insert( rFormat.GetFont(FNT_FUNCTION) ); + m_xNumberFont->Insert( rFormat.GetFont(FNT_NUMBER) ); + m_xTextFont->Insert( rFormat.GetFont(FNT_TEXT) ); + m_xSerifFont->Insert( rFormat.GetFont(FNT_SERIF) ); + m_xSansFont->Insert( rFormat.GetFont(FNT_SANS) ); + m_xFixedFont->Insert( rFormat.GetFont(FNT_FIXED) ); +} + + +void SmFontTypeDialog::WriteTo(SmFormat &rFormat) const +{ + SmModule *pp = SM_MOD(); + + pp->GetConfig()->GetFontPickList(FNT_MATH) = *m_xMathFont; + pp->GetConfig()->GetFontPickList(FNT_VARIABLE) = *m_xVariableFont; + pp->GetConfig()->GetFontPickList(FNT_FUNCTION) = *m_xFunctionFont; + pp->GetConfig()->GetFontPickList(FNT_NUMBER) = *m_xNumberFont; + pp->GetConfig()->GetFontPickList(FNT_TEXT) = *m_xTextFont; + pp->GetConfig()->GetFontPickList(FNT_SERIF) = *m_xSerifFont; + pp->GetConfig()->GetFontPickList(FNT_SANS) = *m_xSansFont; + pp->GetConfig()->GetFontPickList(FNT_FIXED) = *m_xFixedFont; + + rFormat.SetFont( FNT_MATH, m_xMathFont->Get() ); + rFormat.SetFont( FNT_VARIABLE, m_xVariableFont->Get() ); + rFormat.SetFont( FNT_FUNCTION, m_xFunctionFont->Get() ); + rFormat.SetFont( FNT_NUMBER, m_xNumberFont->Get() ); + rFormat.SetFont( FNT_TEXT, m_xTextFont->Get() ); + rFormat.SetFont( FNT_SERIF, m_xSerifFont->Get() ); + rFormat.SetFont( FNT_SANS, m_xSansFont->Get() ); + rFormat.SetFont( FNT_FIXED, m_xFixedFont->Get() ); + + rFormat.RequestApplyChanges(); +} + +/**************************************************************************/ + +namespace { + +struct FieldMinMax +{ + sal_uInt16 nMin, nMax; +}; + +} + +// Data for min and max values of the 4 metric fields +// for each of the 10 categories +const FieldMinMax pMinMaxData[10][4] = +{ + // 0 + {{ 0, 200 }, { 0, 200 }, { 0, 100 }, { 0, 0 }}, + // 1 + {{ 0, 100 }, { 0, 100 }, { 0, 0 }, { 0, 0 }}, + // 2 + {{ 0, 100 }, { 0, 100 }, { 0, 0 }, { 0, 0 }}, + // 3 + {{ 0, 100 }, { 1, 100 }, { 0, 0 }, { 0, 0 }}, + // 4 + {{ 0, 100 }, { 0, 100 }, { 0, 0 }, { 0, 0 }}, + // 5 + {{ 0, 100 }, { 0, 100 }, { 0, 0 }, { 0, 100 }}, + // 6 + {{ 0, 300 }, { 0, 300 }, { 0, 0 }, { 0, 0 }}, + // 7 + {{ 0, 100 }, { 0, 100 }, { 0, 0 }, { 0, 0 }}, + // 8 + {{ 0, 100 }, { 0, 100 }, { 0, 0 }, { 0, 0 }}, + // 9 + {{ 0, 10000 }, { 0, 10000 }, { 0, 10000 }, { 0, 10000 }} +}; + +SmCategoryDesc::SmCategoryDesc(weld::Builder& rBuilder, sal_uInt16 nCategoryIdx) +{ + ++nCategoryIdx; + std::unique_ptr<weld::Label> xTitle(rBuilder.weld_label(OUString::number(nCategoryIdx)+"title")); + if (xTitle) + { + Name = xTitle->get_label(); + } + for (int i = 0; i < 4; ++i) + { + std::unique_ptr<weld::Label> xLabel(rBuilder.weld_label(OUString::number(nCategoryIdx)+"label"+OUString::number(i+1))); + + if (xLabel) + { + Strings[i] = xLabel->get_label(); + Graphics[i] = rBuilder.weld_widget(OUString::number(nCategoryIdx)+"image"+OUString::number(i+1)); + } + else + { + Strings[i].clear(); + Graphics[i].reset(); + } + + const FieldMinMax& rMinMax = pMinMaxData[ nCategoryIdx-1 ][i]; + Value[i] = Minimum[i] = rMinMax.nMin; + Maximum[i] = rMinMax.nMax; + } +} + +SmCategoryDesc::~SmCategoryDesc() +{ +} + +/**************************************************************************/ + +IMPL_LINK( SmDistanceDialog, GetFocusHdl, weld::Widget&, rControl, void ) +{ + if (!m_xCategories[nActiveCategory]) + return; + + sal_uInt16 i; + + if (&rControl == &m_xMetricField1->get_widget()) + i = 0; + else if (&rControl == &m_xMetricField2->get_widget()) + i = 1; + else if (&rControl == &m_xMetricField3->get_widget()) + i = 2; + else if (&rControl == &m_xMetricField4->get_widget()) + i = 3; + else + return; + if (m_pCurrentImage) + m_pCurrentImage->hide(); + m_pCurrentImage = m_xCategories[nActiveCategory]->GetGraphic(i); + m_pCurrentImage->show(); +} + +IMPL_LINK(SmDistanceDialog, MenuSelectHdl, const OUString&, rId, void) +{ + assert(rId.startsWith("menuitem")); + SetCategory(rId.replaceFirst("menuitem", "").toInt32() - 1); +} + +IMPL_LINK_NOARG( SmDistanceDialog, DefaultButtonClickHdl, weld::Button&, void ) +{ + SaveDefaultsQuery aQuery(m_xDialog.get()); + if (aQuery.run() == RET_YES) + { + SmModule *pp = SM_MOD(); + SmFormat aFmt( pp->GetConfig()->GetStandardFormat() ); + WriteTo( aFmt ); + pp->GetConfig()->SetStandardFormat( aFmt ); + } +} + +IMPL_LINK( SmDistanceDialog, CheckBoxClickHdl, weld::Toggleable&, rCheckBox, void ) +{ + if (&rCheckBox == m_xCheckBox1.get()) + { + bool bChecked = m_xCheckBox1->get_active(); + m_xFixedText4->set_sensitive( bChecked ); + m_xMetricField4->set_sensitive( bChecked ); + } +} + +void SmDistanceDialog::SetCategory(sal_uInt16 nCategory) +{ + assert(nCategory < NOCATEGORIES && "Sm: wrong category number in SmDistanceDialog"); + + // array to convert category- and metricfield-number in help ids. + // 0 is used in case of unused combinations. + assert(NOCATEGORIES == 10 && "Sm : array doesn't fit into the number of categories"); + static constexpr OUString EMPTY(u""_ustr); + static constexpr OUString aCatMf2Hid[10][4] = + { + { HID_SMA_DEFAULT_DIST, HID_SMA_LINE_DIST, HID_SMA_ROOT_DIST, EMPTY }, + { HID_SMA_SUP_DIST, HID_SMA_SUB_DIST , EMPTY, EMPTY }, + { HID_SMA_NUMERATOR_DIST, HID_SMA_DENOMINATOR_DIST, EMPTY, EMPTY }, + { HID_SMA_FRACLINE_EXCWIDTH, HID_SMA_FRACLINE_LINEWIDTH, EMPTY, EMPTY }, + { HID_SMA_UPPERLIMIT_DIST, HID_SMA_LOWERLIMIT_DIST, EMPTY, EMPTY }, + { HID_SMA_BRACKET_EXCHEIGHT, HID_SMA_BRACKET_DIST, EMPTY, HID_SMA_BRACKET_EXCHEIGHT2 }, + { HID_SMA_MATRIXROW_DIST, HID_SMA_MATRIXCOL_DIST, EMPTY, EMPTY }, + { HID_SMA_ATTRIBUT_DIST, HID_SMA_INTERATTRIBUT_DIST, EMPTY, EMPTY }, + { HID_SMA_OPERATOR_EXCHEIGHT, HID_SMA_OPERATOR_DIST, EMPTY, EMPTY }, + { HID_SMA_LEFTBORDER_DIST, HID_SMA_RIGHTBORDER_DIST, HID_SMA_UPPERBORDER_DIST, HID_SMA_LOWERBORDER_DIST } + }; + + // array to help iterate over the controls + std::pair<weld::Label*, weld::MetricSpinButton*> const aWin[4] = + { + { m_xFixedText1.get(), m_xMetricField1.get() }, + { m_xFixedText2.get(), m_xMetricField2.get() }, + { m_xFixedText3.get(), m_xMetricField3.get() }, + { m_xFixedText4.get(), m_xMetricField4.get() } + }; + + SmCategoryDesc *pCat; + + // remember the (maybe new) settings of the active SmCategoryDesc + // before switching to the new one + if (nActiveCategory != CATEGORY_NONE) + { + pCat = m_xCategories[nActiveCategory].get(); + pCat->SetValue(0, sal::static_int_cast<sal_uInt16>(m_xMetricField1->get_value(FieldUnit::NONE))); + pCat->SetValue(1, sal::static_int_cast<sal_uInt16>(m_xMetricField2->get_value(FieldUnit::NONE))); + pCat->SetValue(2, sal::static_int_cast<sal_uInt16>(m_xMetricField3->get_value(FieldUnit::NONE))); + pCat->SetValue(3, sal::static_int_cast<sal_uInt16>(m_xMetricField4->get_value(FieldUnit::NONE))); + + if (nActiveCategory == 5) + bScaleAllBrackets = m_xCheckBox1->get_active(); + + m_xMenuButton->set_item_active("menuitem" + OUString::number(nActiveCategory + 1), false); + } + + // activation/deactivation of the associated controls depending on the chosen category + bool bActive; + for (sal_uInt16 i = 0; i < 4; i++) + { + weld::Label *pFT = aWin[i].first; + weld::MetricSpinButton *pMF = aWin[i].second; + + // To determine which Controls should be active, the existence + // of an associated HelpID is checked + bActive = !aCatMf2Hid[nCategory][i].isEmpty(); + + pFT->set_visible(bActive); + pFT->set_sensitive(bActive); + pMF->set_visible(bActive); + pMF->set_sensitive(bActive); + + // set measurement unit and number of decimal places + FieldUnit eUnit; + sal_uInt16 nDigits; + if (nCategory < 9) + { + eUnit = FieldUnit::PERCENT; + nDigits = 0; + } + else + { + eUnit = FieldUnit::MM_100TH; + nDigits = 2; + } + pMF->set_unit(eUnit); // changes the value + pMF->set_digits(nDigits); + + if (bActive) + { + pCat = m_xCategories[nCategory].get(); + pFT->set_label(pCat->GetString(i)); + + pMF->set_range(pCat->GetMinimum(i), pCat->GetMaximum(i), FieldUnit::NONE); + pMF->set_value(pCat->GetValue(i), FieldUnit::NONE); + + pMF->set_help_id(aCatMf2Hid[nCategory][i]); + } + } + // activate the CheckBox and the associated MetricField if we're dealing with the brackets menu + bActive = nCategory == 5; + m_xCheckBox1->set_visible(bActive); + m_xCheckBox1->set_sensitive(bActive); + if (bActive) + { + m_xCheckBox1->set_active(bScaleAllBrackets); + + bool bChecked = m_xCheckBox1->get_active(); + m_xFixedText4->set_sensitive( bChecked ); + m_xMetricField4->set_sensitive( bChecked ); + } + + m_xMenuButton->set_item_active("menuitem" + OUString::number(nCategory + 1), true); + m_xFrame->set_label(m_xCategories[nCategory]->GetName()); + + nActiveCategory = nCategory; + + m_xMetricField1->grab_focus(); +} + +SmDistanceDialog::SmDistanceDialog(weld::Window *pParent) + : GenericDialogController(pParent, "modules/smath/ui/spacingdialog.ui", "SpacingDialog") + , m_xFrame(m_xBuilder->weld_frame("template")) + , m_xFixedText1(m_xBuilder->weld_label("label1")) + , m_xMetricField1(m_xBuilder->weld_metric_spin_button("spinbutton1", FieldUnit::CM)) + , m_xFixedText2(m_xBuilder->weld_label("label2")) + , m_xMetricField2(m_xBuilder->weld_metric_spin_button("spinbutton2", FieldUnit::CM)) + , m_xFixedText3(m_xBuilder->weld_label("label3")) + , m_xMetricField3(m_xBuilder->weld_metric_spin_button("spinbutton3", FieldUnit::CM)) + , m_xCheckBox1(m_xBuilder->weld_check_button("checkbutton")) + , m_xFixedText4(m_xBuilder->weld_label("label4")) + , m_xMetricField4(m_xBuilder->weld_metric_spin_button("spinbutton4", FieldUnit::CM)) + , m_xMenuButton(m_xBuilder->weld_menu_button("category")) + , m_xDefaultButton(m_xBuilder->weld_button("default")) + , m_xBitmap(m_xBuilder->weld_widget("image")) + , m_pCurrentImage(m_xBitmap.get()) +{ + for (sal_uInt16 i = 0; i < NOCATEGORIES; ++i) + m_xCategories[i].reset( new SmCategoryDesc(*m_xBuilder, i) ); + nActiveCategory = CATEGORY_NONE; + bScaleAllBrackets = false; + + m_xMetricField1->connect_focus_in(LINK(this, SmDistanceDialog, GetFocusHdl)); + m_xMetricField2->connect_focus_in(LINK(this, SmDistanceDialog, GetFocusHdl)); + m_xMetricField3->connect_focus_in(LINK(this, SmDistanceDialog, GetFocusHdl)); + m_xMetricField4->connect_focus_in(LINK(this, SmDistanceDialog, GetFocusHdl)); + m_xCheckBox1->connect_toggled(LINK(this, SmDistanceDialog, CheckBoxClickHdl)); + m_xMenuButton->connect_selected(LINK(this, SmDistanceDialog, MenuSelectHdl)); + m_xDefaultButton->connect_clicked(LINK(this, SmDistanceDialog, DefaultButtonClickHdl)); + + //set the initial size, with max visible widgets visible, as preferred size + m_xDialog->set_size_request(-1, m_xDialog->get_preferred_size().Height()); +} + +SmDistanceDialog::~SmDistanceDialog() +{ +} + +void SmDistanceDialog::ReadFrom(const SmFormat &rFormat) +{ + m_xCategories[0]->SetValue(0, rFormat.GetDistance(DIS_HORIZONTAL)); + m_xCategories[0]->SetValue(1, rFormat.GetDistance(DIS_VERTICAL)); + m_xCategories[0]->SetValue(2, rFormat.GetDistance(DIS_ROOT)); + m_xCategories[1]->SetValue(0, rFormat.GetDistance(DIS_SUPERSCRIPT)); + m_xCategories[1]->SetValue(1, rFormat.GetDistance(DIS_SUBSCRIPT)); + m_xCategories[2]->SetValue(0, rFormat.GetDistance(DIS_NUMERATOR)); + m_xCategories[2]->SetValue(1, rFormat.GetDistance(DIS_DENOMINATOR)); + m_xCategories[3]->SetValue(0, rFormat.GetDistance(DIS_FRACTION)); + m_xCategories[3]->SetValue(1, rFormat.GetDistance(DIS_STROKEWIDTH)); + m_xCategories[4]->SetValue(0, rFormat.GetDistance(DIS_UPPERLIMIT)); + m_xCategories[4]->SetValue(1, rFormat.GetDistance(DIS_LOWERLIMIT)); + m_xCategories[5]->SetValue(0, rFormat.GetDistance(DIS_BRACKETSIZE)); + m_xCategories[5]->SetValue(1, rFormat.GetDistance(DIS_BRACKETSPACE)); + m_xCategories[5]->SetValue(3, rFormat.GetDistance(DIS_NORMALBRACKETSIZE)); + m_xCategories[6]->SetValue(0, rFormat.GetDistance(DIS_MATRIXROW)); + m_xCategories[6]->SetValue(1, rFormat.GetDistance(DIS_MATRIXCOL)); + m_xCategories[7]->SetValue(0, rFormat.GetDistance(DIS_ORNAMENTSIZE)); + m_xCategories[7]->SetValue(1, rFormat.GetDistance(DIS_ORNAMENTSPACE)); + m_xCategories[8]->SetValue(0, rFormat.GetDistance(DIS_OPERATORSIZE)); + m_xCategories[8]->SetValue(1, rFormat.GetDistance(DIS_OPERATORSPACE)); + m_xCategories[9]->SetValue(0, rFormat.GetDistance(DIS_LEFTSPACE)); + m_xCategories[9]->SetValue(1, rFormat.GetDistance(DIS_RIGHTSPACE)); + m_xCategories[9]->SetValue(2, rFormat.GetDistance(DIS_TOPSPACE)); + m_xCategories[9]->SetValue(3, rFormat.GetDistance(DIS_BOTTOMSPACE)); + + bScaleAllBrackets = rFormat.IsScaleNormalBrackets(); + + // force update (even of category 0) by setting nActiveCategory to a + // non-existent category number + nActiveCategory = CATEGORY_NONE; + SetCategory(0); +} + + +void SmDistanceDialog::WriteTo(SmFormat &rFormat) /*const*/ +{ + // TODO can they actually be different? + // if that's not the case 'const' could be used above! + SetCategory(nActiveCategory); + + rFormat.SetDistance( DIS_HORIZONTAL, m_xCategories[0]->GetValue(0) ); + rFormat.SetDistance( DIS_VERTICAL, m_xCategories[0]->GetValue(1) ); + rFormat.SetDistance( DIS_ROOT, m_xCategories[0]->GetValue(2) ); + rFormat.SetDistance( DIS_SUPERSCRIPT, m_xCategories[1]->GetValue(0) ); + rFormat.SetDistance( DIS_SUBSCRIPT, m_xCategories[1]->GetValue(1) ); + rFormat.SetDistance( DIS_NUMERATOR, m_xCategories[2]->GetValue(0) ); + rFormat.SetDistance( DIS_DENOMINATOR, m_xCategories[2]->GetValue(1) ); + rFormat.SetDistance( DIS_FRACTION, m_xCategories[3]->GetValue(0) ); + rFormat.SetDistance( DIS_STROKEWIDTH, m_xCategories[3]->GetValue(1) ); + rFormat.SetDistance( DIS_UPPERLIMIT, m_xCategories[4]->GetValue(0) ); + rFormat.SetDistance( DIS_LOWERLIMIT, m_xCategories[4]->GetValue(1) ); + rFormat.SetDistance( DIS_BRACKETSIZE, m_xCategories[5]->GetValue(0) ); + rFormat.SetDistance( DIS_BRACKETSPACE, m_xCategories[5]->GetValue(1) ); + rFormat.SetDistance( DIS_MATRIXROW, m_xCategories[6]->GetValue(0) ); + rFormat.SetDistance( DIS_MATRIXCOL, m_xCategories[6]->GetValue(1) ); + rFormat.SetDistance( DIS_ORNAMENTSIZE, m_xCategories[7]->GetValue(0) ); + rFormat.SetDistance( DIS_ORNAMENTSPACE, m_xCategories[7]->GetValue(1) ); + rFormat.SetDistance( DIS_OPERATORSIZE, m_xCategories[8]->GetValue(0) ); + rFormat.SetDistance( DIS_OPERATORSPACE, m_xCategories[8]->GetValue(1) ); + rFormat.SetDistance( DIS_LEFTSPACE, m_xCategories[9]->GetValue(0) ); + rFormat.SetDistance( DIS_RIGHTSPACE, m_xCategories[9]->GetValue(1) ); + rFormat.SetDistance( DIS_TOPSPACE, m_xCategories[9]->GetValue(2) ); + rFormat.SetDistance( DIS_BOTTOMSPACE, m_xCategories[9]->GetValue(3) ); + rFormat.SetDistance( DIS_NORMALBRACKETSIZE, m_xCategories[5]->GetValue(3) ); + + rFormat.SetScaleNormalBrackets( bScaleAllBrackets ); + + rFormat.RequestApplyChanges(); +} + +IMPL_LINK_NOARG( SmAlignDialog, DefaultButtonClickHdl, weld::Button&, void ) +{ + SaveDefaultsQuery aQuery(m_xDialog.get()); + if (aQuery.run() == RET_YES) + { + SmModule *pp = SM_MOD(); + SmFormat aFmt( pp->GetConfig()->GetStandardFormat() ); + WriteTo( aFmt ); + pp->GetConfig()->SetStandardFormat( aFmt ); + } +} + +SmAlignDialog::SmAlignDialog(weld::Window* pParent) + : GenericDialogController(pParent, "modules/smath/ui/alignmentdialog.ui", "AlignmentDialog") + , m_xLeft(m_xBuilder->weld_radio_button("left")) + , m_xCenter(m_xBuilder->weld_radio_button("center")) + , m_xRight(m_xBuilder->weld_radio_button("right")) + , m_xDefaultButton(m_xBuilder->weld_button("default")) +{ + m_xDefaultButton->connect_clicked(LINK(this, SmAlignDialog, DefaultButtonClickHdl)); +} + +SmAlignDialog::~SmAlignDialog() +{ +} + +void SmAlignDialog::ReadFrom(const SmFormat &rFormat) +{ + switch (rFormat.GetHorAlign()) + { + case SmHorAlign::Left: + m_xLeft->set_active(true); + break; + case SmHorAlign::Center: + m_xCenter->set_active(true); + break; + case SmHorAlign::Right: + m_xRight->set_active(true); + break; + } +} + +void SmAlignDialog::WriteTo(SmFormat &rFormat) const +{ + if (m_xLeft->get_active()) + rFormat.SetHorAlign(SmHorAlign::Left); + else if (m_xRight->get_active()) + rFormat.SetHorAlign(SmHorAlign::Right); + else + rFormat.SetHorAlign(SmHorAlign::Center); + + rFormat.RequestApplyChanges(); +} + +SmShowSymbolSet::SmShowSymbolSet(std::unique_ptr<weld::ScrolledWindow> pScrolledWindow, SmViewShell &rViewShell) + : m_rViewShell(rViewShell) + , nLen(0) + , nRows(0) + , nColumns(0) + , nXOffset(0) + , nYOffset(0) + , nSelectSymbol(SYMBOL_NONE) + , m_xScrolledWindow(std::move(pScrolledWindow)) +{ + m_xScrolledWindow->connect_vadjustment_changed(LINK(this, SmShowSymbolSet, ScrollHdl)); +} + +Point SmShowSymbolSet::OffsetPoint(const Point &rPoint) const +{ + return Point(rPoint.X() + nXOffset, rPoint.Y() + nYOffset); +} + +void SmShowSymbolSet::Resize() +{ + CustomWidgetController::Resize(); + Size aWinSize(GetOutputSizePixel()); + if (aWinSize != m_aOldSize) + { + calccols(GetDrawingArea()->get_ref_device()); + m_aOldSize = aWinSize; + } +} + +void SmShowSymbolSet::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&) +{ + Color aBackgroundColor; + Color aTextColor; + lclGetSettingColors(aBackgroundColor, aTextColor); + + rRenderContext.SetBackground(Wallpaper(aBackgroundColor)); + rRenderContext.SetTextColor(aTextColor); + + rRenderContext.Push(vcl::PushFlags::MAPMODE); + + // set MapUnit for which 'nLen' has been calculated + rRenderContext.SetMapMode(MapMode(MapUnit::MapPixel)); + + sal_uInt16 v = sal::static_int_cast< sal_uInt16 >(m_xScrolledWindow->vadjustment_get_value() * nColumns); + size_t nSymbols = aSymbolSet.size(); + + Color aTxtColor(rRenderContext.GetTextColor()); + for (size_t i = v; i < nSymbols ; i++) + { + SmSym aSymbol(*aSymbolSet[i]); + vcl::Font aFont(lclGetSymbolFont(m_rViewShell, aSymbol)); + aFont.SetAlignment(ALIGN_TOP); + + // taking a FontSize which is a bit smaller (compared to nLen) in order to have a buffer + // (hopefully enough for left and right, too) + aFont.SetFontSize(Size(0, nLen - (nLen / 3))); + rRenderContext.SetFont(aFont); + // keep text color + rRenderContext.SetTextColor(aTxtColor); + + int nIV = i - v; + sal_UCS4 cChar = aSymbol.GetCharacter(); + OUString aText(&cChar, 1); + Size aSize(rRenderContext.GetTextWidth( aText ), rRenderContext.GetTextHeight()); + + Point aPoint((nIV % nColumns) * nLen + (nLen - aSize.Width()) / 2, + (nIV / nColumns) * nLen + (nLen - aSize.Height()) / 2); + + rRenderContext.DrawText(OffsetPoint(aPoint), aText); + } + + if (nSelectSymbol != SYMBOL_NONE) + { + Point aPoint(((nSelectSymbol - v) % nColumns) * nLen, + ((nSelectSymbol - v) / nColumns) * nLen); + + rRenderContext.Invert(tools::Rectangle(OffsetPoint(aPoint), Size(nLen, nLen))); + + } + + rRenderContext.Pop(); +} + +bool SmShowSymbolSet::MouseButtonDown(const MouseEvent& rMEvt) +{ + GrabFocus(); + + Size aOutputSize(nColumns * nLen, nRows * nLen); + aOutputSize.AdjustWidth(nXOffset ); + aOutputSize.AdjustHeight(nYOffset ); + Point aPoint(rMEvt.GetPosPixel()); + aPoint.AdjustX( -nXOffset ); + aPoint.AdjustY( -nYOffset ); + + if (rMEvt.IsLeft() && tools::Rectangle(Point(0, 0), aOutputSize).Contains(rMEvt.GetPosPixel())) + { + tools::Long nPos = (aPoint.Y() / nLen) * nColumns + (aPoint.X() / nLen) + + m_xScrolledWindow->vadjustment_get_value() * nColumns; + SelectSymbol( sal::static_int_cast< sal_uInt16 >(nPos) ); + + aSelectHdlLink.Call(*this); + + if (rMEvt.GetClicks() > 1) + aDblClickHdlLink.Call(*this); + } + + return true; +} + +bool SmShowSymbolSet::KeyInput(const KeyEvent& rKEvt) +{ + sal_uInt16 n = nSelectSymbol; + + if (n != SYMBOL_NONE) + { + switch (rKEvt.GetKeyCode().GetCode()) + { + case KEY_DOWN: n = n + nColumns; break; + case KEY_UP: n = n - nColumns; break; + case KEY_LEFT: n -= 1; break; + case KEY_RIGHT: n += 1; break; + case KEY_HOME: n = 0; break; + case KEY_END: n = static_cast< sal_uInt16 >(aSymbolSet.size() - 1); break; + case KEY_PAGEUP: n -= nColumns * nRows; break; + case KEY_PAGEDOWN: n += nColumns * nRows; break; + default: + return false; + } + } + else + n = 0; + + if (n >= aSymbolSet.size()) + n = nSelectSymbol; + + // adjust scrollbar + if ((n < sal::static_int_cast<sal_uInt16>(m_xScrolledWindow->vadjustment_get_value() * nColumns)) || + (n >= sal::static_int_cast<sal_uInt16>((m_xScrolledWindow->vadjustment_get_value() + nRows) * nColumns))) + { + m_xScrolledWindow->vadjustment_set_value(n / nColumns); + Invalidate(); + } + + SelectSymbol(n); + aSelectHdlLink.Call(*this); + + return true; +} + +void SmShowSymbolSet::calccols(const vcl::RenderContext& rRenderContext) +{ + // Height of 16pt in pixels (matching 'aOutputSize') + nLen = rRenderContext.LogicToPixel(Size(0, 16), MapMode(MapUnit::MapPoint)).Height(); + + Size aOutputSize(GetOutputSizePixel()); + + nColumns = aOutputSize.Width() / nLen; + nRows = aOutputSize.Height() / nLen; + nColumns = std::max<tools::Long>(1, nColumns); + nRows = std::max<tools::Long>(1, nRows); + + nXOffset = (aOutputSize.Width() - (nColumns * nLen)) / 2; + nYOffset = (aOutputSize.Height() - (nRows * nLen)) / 2; + + SetScrollBarRange(); +} + +void SmShowSymbolSet::SetSymbolSet(const SymbolPtrVec_t & rSymbolSet) +{ + aSymbolSet = rSymbolSet; + SetScrollBarRange(); + Invalidate(); +} + +void SmShowSymbolSet::SetScrollBarRange() +{ + const int nLastRow = (aSymbolSet.size() - 1 + nColumns) / nColumns; + m_xScrolledWindow->vadjustment_configure(m_xScrolledWindow->vadjustment_get_value(), 0, nLastRow, 1, nRows - 1, nRows); + Invalidate(); +} + +void SmShowSymbolSet::SelectSymbol(sal_uInt16 nSymbol) +{ + int v = m_xScrolledWindow->vadjustment_get_value() * nColumns; + + if (nSelectSymbol != SYMBOL_NONE && nColumns) + { + Point aPoint(OffsetPoint(Point(((nSelectSymbol - v) % nColumns) * nLen, + ((nSelectSymbol - v) / nColumns) * nLen))); + Invalidate(tools::Rectangle(aPoint, Size(nLen, nLen))); + } + + if (nSymbol < aSymbolSet.size()) + nSelectSymbol = nSymbol; + + if (aSymbolSet.empty()) + nSelectSymbol = SYMBOL_NONE; + + if (nSelectSymbol != SYMBOL_NONE && nColumns) + { + Point aPoint(OffsetPoint(Point(((nSelectSymbol - v) % nColumns) * nLen, + ((nSelectSymbol - v) / nColumns) * nLen))); + Invalidate(tools::Rectangle(aPoint, Size(nLen, nLen))); + } + + if (!nColumns) + Invalidate(); +} + +IMPL_LINK_NOARG(SmShowSymbolSet, ScrollHdl, weld::ScrolledWindow&, void) +{ + Invalidate(); +} + +SmShowSymbol::SmShowSymbol(SmViewShell& rViewShell) + : m_rViewShell(rViewShell) +{ +} + +void SmShowSymbol::setFontSize(vcl::Font &rFont) const +{ + Size aSize(GetOutputSizePixel()); + rFont.SetFontSize(Size(0, aSize.Height() - aSize.Height() / 3)); +} + +void SmShowSymbol::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&) +{ + Color aBackgroundColor; + Color aTextColor; + lclGetSettingColors(aBackgroundColor, aTextColor); + rRenderContext.SetBackground(Wallpaper(aBackgroundColor)); + rRenderContext.SetTextColor(aTextColor); + rRenderContext.Erase(); + + vcl::Font aFont(GetFont()); + setFontSize(aFont); + rRenderContext.SetFont(aFont); + + const OUString &rText = GetText(); + Size aTextSize(rRenderContext.GetTextWidth(rText), rRenderContext.GetTextHeight()); + + rRenderContext.DrawText(Point((rRenderContext.GetOutputSize().Width() - aTextSize.Width()) / 2, + (rRenderContext.GetOutputSize().Height() * 7 / 10)), rText); +} + +bool SmShowSymbol::MouseButtonDown(const MouseEvent& rMEvt) +{ + if (rMEvt.GetClicks() > 1) + aDblClickHdlLink.Call(*this); + return true; +} + +void SmShowSymbol::SetSymbol(const SmSym *pSymbol) +{ + if (pSymbol) + { + vcl::Font aFont(lclGetSymbolFont(m_rViewShell, *pSymbol)); + aFont.SetAlignment(ALIGN_BASELINE); + SetFont(aFont); + + sal_UCS4 cChar = pSymbol->GetCharacter(); + OUString aText(&cChar, 1); + SetText( aText ); + } + + Invalidate(); +} + +void SmSymbolDialog::FillSymbolSets() + // populate the entries of possible SymbolsSets in the dialog with + // current values of the SymbolSet manager but selects none of those +{ + m_xSymbolSets->clear(); + m_xSymbolSets->set_active(-1); + + std::set< OUString > aSymbolSetNames( rSymbolMgr.GetSymbolSetNames() ); + for (const auto& rSymbolSetName : aSymbolSetNames) + m_xSymbolSets->append_text(rSymbolSetName); +} + +IMPL_LINK_NOARG( SmSymbolDialog, SymbolSetChangeHdl, weld::ComboBox&, void ) +{ + SelectSymbolSet(m_xSymbolSets->get_active_text()); +} + +IMPL_LINK_NOARG( SmSymbolDialog, SymbolChangeHdl, SmShowSymbolSet&, void ) +{ + SelectSymbol(m_xSymbolSetDisplay->GetSelectSymbol()); +} + +IMPL_LINK_NOARG(SmSymbolDialog, EditClickHdl, weld::Button&, void) +{ + SmSymDefineDialog aDialog(m_xDialog.get(), pFontListDev, rSymbolMgr); + + // set current symbol and SymbolSet for the new dialog + const OUString aSymSetName (m_xSymbolSets->get_active_text()), + aSymName (m_xSymbolName->get_label()); + aDialog.SelectOldSymbolSet(aSymSetName); + aDialog.SelectOldSymbol(aSymName); + aDialog.SelectSymbolSet(aSymSetName); + aDialog.SelectSymbol(aSymName); + + // remember old SymbolSet + OUString aOldSymbolSet (m_xSymbolSets->get_active_text()); + + sal_uInt16 nSymPos = m_xSymbolSetDisplay->GetSelectSymbol(); + + // adapt dialog to data of the SymbolSet manager, which might have changed + if (aDialog.run() == RET_OK && rSymbolMgr.IsModified()) + { + rSymbolMgr.Save(); + FillSymbolSets(); + } + + // if the old SymbolSet doesn't exist anymore, go to the first one SymbolSet (if one exists) + if (!SelectSymbolSet(aOldSymbolSet) && m_xSymbolSets->get_count() > 0) + SelectSymbolSet(m_xSymbolSets->get_text(0)); + else + { + // just update display of current symbol set + assert(aSymSetName == aSymSetName); //unexpected change in symbol set name + aSymbolSet = rSymbolMgr.GetSymbolSet( aSymbolSetName ); + m_xSymbolSetDisplay->SetSymbolSet( aSymbolSet ); + } + + if (nSymPos >= aSymbolSet.size()) + nSymPos = static_cast< sal_uInt16 >(aSymbolSet.size()) - 1; + SelectSymbol( nSymPos ); +} + +IMPL_LINK_NOARG( SmSymbolDialog, SymbolDblClickHdl2, SmShowSymbolSet&, void ) +{ + SymbolDblClickHdl(); +} + +IMPL_LINK_NOARG( SmSymbolDialog, SymbolDblClickHdl, SmShowSymbol&, void ) +{ + SymbolDblClickHdl(); +} + +void SmSymbolDialog::SymbolDblClickHdl() +{ + GetClickHdl(*m_xGetBtn); + m_xDialog->response(RET_OK); +} + +IMPL_LINK_NOARG(SmSymbolDialog, GetClickHdl, weld::Button&, void) +{ + const SmSym *pSym = GetSymbol(); + if (pSym) + { + OUString aText = "%" + pSym->GetUiName() + " "; + + rViewSh.GetViewFrame().GetDispatcher()->ExecuteList( + SID_INSERTSPECIAL, SfxCallMode::RECORD, + { new SfxStringItem(SID_INSERTSPECIAL, aText) }); + } +} + +SmSymbolDialog::SmSymbolDialog(weld::Window *pParent, OutputDevice *pFntListDevice, + SmSymbolManager &rMgr, SmViewShell &rViewShell) + : GenericDialogController(pParent, "modules/smath/ui/catalogdialog.ui", "CatalogDialog") + , rViewSh(rViewShell) + , rSymbolMgr(rMgr) + , pFontListDev(pFntListDevice) + , m_aSymbolDisplay(rViewShell) + , m_xSymbolSets(m_xBuilder->weld_combo_box("symbolset")) + , m_xSymbolSetDisplay(new SmShowSymbolSet(m_xBuilder->weld_scrolled_window("scrolledwindow", true), rViewShell)) + , m_xSymbolSetDisplayArea(new weld::CustomWeld(*m_xBuilder, "symbolsetdisplay", *m_xSymbolSetDisplay)) + , m_xSymbolName(m_xBuilder->weld_label("symbolname")) + , m_xSymbolDisplay(new weld::CustomWeld(*m_xBuilder, "preview", m_aSymbolDisplay)) + , m_xGetBtn(m_xBuilder->weld_button("ok")) + , m_xEditBtn(m_xBuilder->weld_button("edit")) +{ + m_xSymbolSets->make_sorted(); + + aSymbolSetName.clear(); + aSymbolSet.clear(); + FillSymbolSets(); + if (m_xSymbolSets->get_count() > 0) + SelectSymbolSet(m_xSymbolSets->get_text(0)); + + m_xSymbolSets->connect_changed(LINK(this, SmSymbolDialog, SymbolSetChangeHdl)); + m_xSymbolSetDisplay->SetSelectHdl(LINK(this, SmSymbolDialog, SymbolChangeHdl)); + m_xSymbolSetDisplay->SetDblClickHdl(LINK(this, SmSymbolDialog, SymbolDblClickHdl2)); + m_aSymbolDisplay.SetDblClickHdl(LINK(this, SmSymbolDialog, SymbolDblClickHdl)); + m_xEditBtn->connect_clicked(LINK(this, SmSymbolDialog, EditClickHdl)); + m_xGetBtn->connect_clicked(LINK(this, SmSymbolDialog, GetClickHdl)); +} + +SmSymbolDialog::~SmSymbolDialog() +{ +} + +bool SmSymbolDialog::SelectSymbolSet(const OUString &rSymbolSetName) +{ + bool bRet = false; + sal_Int32 nPos = m_xSymbolSets->find_text(rSymbolSetName); + + aSymbolSetName.clear(); + aSymbolSet.clear(); + if (nPos != -1) + { + m_xSymbolSets->set_active(nPos); + + aSymbolSetName = rSymbolSetName; + aSymbolSet = rSymbolMgr.GetSymbolSet( aSymbolSetName ); + + // sort symbols by Unicode position (useful for displaying Greek characters alphabetically) + std::sort( aSymbolSet.begin(), aSymbolSet.end(), + [](const SmSym *pSym1, const SmSym *pSym2) + { + return pSym1->GetCharacter() < pSym2->GetCharacter(); + } ); + + const bool bEmptySymbolSet = aSymbolSet.empty(); + m_xSymbolSetDisplay->SetSymbolSet( aSymbolSet ); + if (!bEmptySymbolSet) + SelectSymbol(0); + + bRet = true; + } + else + m_xSymbolSets->set_active(-1); + + return bRet; +} + +void SmSymbolDialog::SelectSymbol(sal_uInt16 nSymbolNo) +{ + const SmSym *pSym = nullptr; + if (!aSymbolSetName.isEmpty() && nSymbolNo < static_cast< sal_uInt16 >(aSymbolSet.size())) + pSym = aSymbolSet[ nSymbolNo ]; + + m_xSymbolSetDisplay->SelectSymbol(nSymbolNo); + m_aSymbolDisplay.SetSymbol(pSym); + m_xSymbolName->set_label(pSym ? pSym->GetUiName() : OUString()); +} + +const SmSym* SmSymbolDialog::GetSymbol() const +{ + sal_uInt16 nSymbolNo = m_xSymbolSetDisplay->GetSelectSymbol(); + bool bValid = !aSymbolSetName.isEmpty() && nSymbolNo < static_cast< sal_uInt16 >(aSymbolSet.size()); + return bValid ? aSymbolSet[ nSymbolNo ] : nullptr; +} + +void SmShowChar::Resize() +{ + const OUString &rText = GetText(); + if (rText.isEmpty()) + return; + sal_UCS4 cChar = rText.iterateCodePoints(&o3tl::temporary(sal_Int32(0))); + SetSymbol(cChar, GetFont()); //force recalculation of size +} + +void SmShowChar::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&) +{ + Color aTextCol = rRenderContext.GetTextColor(); + Color aFillCol = rRenderContext.GetFillColor(); + + const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); + const Color aWindowTextColor(rStyleSettings.GetDialogTextColor()); + const Color aWindowColor(rStyleSettings.GetWindowColor()); + rRenderContext.SetTextColor(aWindowTextColor); + rRenderContext.SetFillColor(aWindowColor); + + Size aSize(GetOutputSizePixel()); + rRenderContext.DrawRect(tools::Rectangle(Point(0, 0), aSize)); + + OUString aText(GetText()); + if (!aText.isEmpty()) + { + vcl::Font aFont(m_aFont); + aFont.SetAlignment(ALIGN_TOP); + rRenderContext.SetFont(aFont); + + Size aTextSize(rRenderContext.GetTextWidth(aText), rRenderContext.GetTextHeight()); + + rRenderContext.DrawText(Point((aSize.Width() - aTextSize.Width()) / 2, + (aSize.Height() - aTextSize.Height()) / 2), aText); + } + + rRenderContext.SetTextColor(aTextCol); + rRenderContext.SetFillColor(aFillCol); +} + +void SmShowChar::SetSymbol( const SmSym *pSym ) +{ + if (pSym) + SetSymbol( pSym->GetCharacter(), pSym->GetFace() ); +} + + +void SmShowChar::SetSymbol( sal_UCS4 cChar, const vcl::Font &rFont ) +{ + vcl::Font aFont( rFont ); + Size aSize(GetOutputSizePixel()); + aFont.SetFontSize(Size(0, aSize.Height() - aSize.Height() / 3)); + aFont.SetAlignment(ALIGN_BASELINE); + SetFont(aFont); + + OUString aText(&cChar, 1); + SetText( aText ); + + Invalidate(); +} + +void SmSymDefineDialog::FillSymbols(weld::ComboBox& rComboBox, bool bDeleteText) +{ + assert((&rComboBox == m_xOldSymbols.get() || &rComboBox == m_xSymbols.get()) && "Sm : wrong ComboBox"); + + rComboBox.clear(); + if (bDeleteText) + rComboBox.set_entry_text(OUString()); + + weld::ComboBox& rBox = &rComboBox == m_xOldSymbols.get() ? *m_xOldSymbolSets : *m_xSymbolSets; + SymbolPtrVec_t aSymSet(m_aSymbolMgrCopy.GetSymbolSet(rBox.get_active_text())); + for (const SmSym* i : aSymSet) + rComboBox.append_text(i->GetUiName()); +} + +void SmSymDefineDialog::FillSymbolSets(weld::ComboBox& rComboBox, bool bDeleteText) +{ + assert((&rComboBox == m_xOldSymbolSets.get() || &rComboBox == m_xSymbolSets.get()) && "Sm : wrong ComboBox"); + + rComboBox.clear(); + if (bDeleteText) + rComboBox.set_entry_text(OUString()); + + const std::set< OUString > aSymbolSetNames( m_aSymbolMgrCopy.GetSymbolSetNames() ); + for (const auto& rSymbolSetName : aSymbolSetNames) + rComboBox.append_text(rSymbolSetName); +} + +void SmSymDefineDialog::FillFonts() +{ + m_xFonts->clear(); + m_xFonts->set_active(-1); + + // Include all fonts of FontList into the font list. + // If there are duplicates, only include one entry of each font since the style will be + // already selected using the FontStyleBox. + if (m_xFontList) + { + sal_uInt16 nCount = m_xFontList->GetFontNameCount(); + for (sal_uInt16 i = 0; i < nCount; ++i) + m_xFonts->append_text(m_xFontList->GetFontName(i).GetFamilyName()); + } +} + +void SmSymDefineDialog::FillStyles() +{ + m_xStyles->clear(); +// pStyles->SetText(OUString()); + + OUString aText(m_xFonts->get_active_text()); + if (!aText.isEmpty()) + { + // use own StyleNames + const SmFontStyles &rStyles = GetFontStyles(); + for (sal_uInt16 i = 0; i < SmFontStyles::GetCount(); ++i) + m_xStyles->append_text(rStyles.GetStyleName(i)); + + assert(m_xStyles->get_count() > 0 && "Sm : no styles available"); + m_xStyles->set_active(0); + } +} + +SmSym* SmSymDefineDialog::GetSymbol(const weld::ComboBox& rComboBox) +{ + assert((&rComboBox == m_xOldSymbols.get() || &rComboBox == m_xSymbols.get()) && "Sm : wrong combobox"); + return m_aSymbolMgrCopy.GetSymbolByUiName(rComboBox.get_active_text()); +} + +IMPL_LINK(SmSymDefineDialog, OldSymbolChangeHdl, weld::ComboBox&, rComboBox, void) +{ + (void) rComboBox; + assert(&rComboBox == m_xOldSymbols.get() && "Sm : wrong argument"); + SelectSymbol(*m_xOldSymbols, m_xOldSymbols->get_active_text(), false); +} + +IMPL_LINK( SmSymDefineDialog, OldSymbolSetChangeHdl, weld::ComboBox&, rComboBox, void ) +{ + (void) rComboBox; + assert(&rComboBox == m_xOldSymbolSets.get() && "Sm : wrong argument"); + SelectSymbolSet(*m_xOldSymbolSets, m_xOldSymbolSets->get_active_text(), false); +} + +IMPL_LINK(SmSymDefineDialog, ModifyHdl, weld::ComboBox&, rComboBox, void) +{ + // remember cursor position for later restoring of it + int nStartPos, nEndPos; + rComboBox.get_entry_selection_bounds(nStartPos, nEndPos); + + if (&rComboBox == m_xSymbols.get()) + SelectSymbol(*m_xSymbols, m_xSymbols->get_active_text(), false); + else if (&rComboBox == m_xSymbolSets.get()) + SelectSymbolSet(*m_xSymbolSets, m_xSymbolSets->get_active_text(), false); + else if (&rComboBox == m_xOldSymbols.get()) + // allow only names from the list + SelectSymbol(*m_xOldSymbols, m_xOldSymbols->get_active_text(), true); + else if (&rComboBox == m_xOldSymbolSets.get()) + // allow only names from the list + SelectSymbolSet(*m_xOldSymbolSets, m_xOldSymbolSets->get_active_text(), true); + else if (&rComboBox == m_xStyles.get()) + // allow only names from the list (that's the case here anyway) + SelectStyle(m_xStyles->get_active_text(), true); + else + SAL_WARN("starmath", "wrong combobox argument"); + + rComboBox.select_entry_region(nStartPos, nEndPos); + + UpdateButtons(); +} + +IMPL_LINK(SmSymDefineDialog, FontChangeHdl, weld::ComboBox&, rListBox, void) +{ + (void) rListBox; + assert(&rListBox == m_xFonts.get() && "Sm : wrong argument"); + + SelectFont(m_xFonts->get_active_text()); +} + +IMPL_LINK_NOARG(SmSymDefineDialog, SubsetChangeHdl, weld::ComboBox&, void) +{ + int nPos = m_xFontsSubsetLB->get_active(); + if (nPos != -1) + { + const Subset* pSubset = weld::fromId<const Subset*>(m_xFontsSubsetLB->get_active_id()); + if (pSubset) + { + m_xCharsetDisplay->SelectCharacter( pSubset->GetRangeMin() ); + } + } +} + +IMPL_LINK( SmSymDefineDialog, StyleChangeHdl, weld::ComboBox&, rComboBox, void ) +{ + (void) rComboBox; + assert(&rComboBox == m_xStyles.get() && "Sm : wrong argument"); + + SelectStyle(m_xStyles->get_active_text()); +} + +IMPL_LINK_NOARG(SmSymDefineDialog, CharHighlightHdl, SvxShowCharSet*, void) +{ + sal_UCS4 cChar = m_xCharsetDisplay->GetSelectCharacter(); + + if (m_xSubsetMap) + { + const Subset* pSubset = m_xSubsetMap->GetSubsetByUnicode(cChar); + if (pSubset) + m_xFontsSubsetLB->set_active_text(pSubset->GetName()); + else + m_xFontsSubsetLB->set_active(-1); + } + + m_aSymbolDisplay.SetSymbol(cChar, m_xCharsetDisplay->GetFont()); + + UpdateButtons(); + + // display Unicode position as symbol name while iterating over characters + const OUString aHex(OUString::number(cChar, 16).toAsciiUpperCase()); + const OUString aPattern( (aHex.getLength() > 4) ? OUString("Ux000000") : OUString("Ux0000") ); + OUString aUnicodePos = aPattern.subView( 0, aPattern.getLength() - aHex.getLength() ) + + aHex; + m_xSymbols->set_entry_text(aUnicodePos); + m_xSymbolName->set_label(aUnicodePos); +} + +IMPL_LINK( SmSymDefineDialog, AddClickHdl, weld::Button&, rButton, void ) +{ + (void) rButton; + assert(&rButton == m_xAddBtn.get() && "Sm : wrong argument"); + assert(rButton.get_sensitive() && "Sm : requirements met ??"); + + // add symbol + const SmSym aNewSymbol(m_xSymbols->get_active_text(), m_xCharsetDisplay->GetFont(), + m_xCharsetDisplay->GetSelectCharacter(), m_xSymbolSets->get_active_text()); + //OSL_ENSURE( m_aSymbolMgrCopy.GetSymbolByUiName(aTmpSymbolName) == NULL, "symbol already exists" ); + m_aSymbolMgrCopy.AddOrReplaceSymbol( aNewSymbol ); + + // update display of new symbol + m_aSymbolDisplay.SetSymbol( &aNewSymbol ); + m_xSymbolName->set_label(aNewSymbol.GetUiName()); + m_xSymbolSetName->set_label(aNewSymbol.GetSymbolSetName()); + + // update list box entries + FillSymbolSets(*m_xOldSymbolSets, false); + FillSymbolSets(*m_xSymbolSets, false); + FillSymbols(*m_xOldSymbols, false); + FillSymbols(*m_xSymbols, false); + + UpdateButtons(); +} + +IMPL_LINK( SmSymDefineDialog, ChangeClickHdl, weld::Button&, rButton, void ) +{ + (void) rButton; + assert(&rButton == m_xChangeBtn.get() && "Sm : wrong argument"); + assert(m_xChangeBtn->get_sensitive() && "Sm : requirements met ??"); + + // get new Symbol to use + //! get font from symbol-disp lay since charset-display does not keep + //! the bold attribute. + const SmSym aNewSymbol(m_xSymbols->get_active_text(), m_xCharsetDisplay->GetFont(), + m_xCharsetDisplay->GetSelectCharacter(), m_xSymbolSets->get_active_text()); + + // remove old symbol if the name was changed then add new one + const bool bNameChanged = m_xOldSymbols->get_active_text() != m_xSymbols->get_active_text(); + if (bNameChanged) + m_aSymbolMgrCopy.RemoveSymbol(m_xOldSymbols->get_active_text()); + m_aSymbolMgrCopy.AddOrReplaceSymbol( aNewSymbol, true ); + + // clear display for original symbol if necessary + if (bNameChanged) + SetOrigSymbol(nullptr, OUString()); + + // update display of new symbol + m_aSymbolDisplay.SetSymbol(&aNewSymbol); + m_xSymbolName->set_label(aNewSymbol.GetUiName()); + m_xSymbolSetName->set_label(aNewSymbol.GetSymbolSetName()); + + // update list box entries + FillSymbolSets(*m_xOldSymbolSets, false); + FillSymbolSets(*m_xSymbolSets, false); + FillSymbols(*m_xOldSymbols, false); + FillSymbols(*m_xSymbols, false); + + UpdateButtons(); +} + +IMPL_LINK(SmSymDefineDialog, DeleteClickHdl, weld::Button&, rButton, void) +{ + (void) rButton; + assert(&rButton == m_xDeleteBtn.get() && "Sm : wrong argument"); + assert(m_xDeleteBtn->get_sensitive() && "Sm : requirements met ??"); + + if (m_xOrigSymbol) + { + m_aSymbolMgrCopy.RemoveSymbol(m_xOrigSymbol->GetUiName()); + + // clear display for original symbol + SetOrigSymbol(nullptr, OUString()); + + // update list box entries + FillSymbolSets(*m_xOldSymbolSets, false); + FillSymbolSets(*m_xSymbolSets, false); + FillSymbols(*m_xOldSymbols ,false); + FillSymbols(*m_xSymbols ,false); + } + + UpdateButtons(); +} + +void SmSymDefineDialog::UpdateButtons() +{ + bool bAdd = false, + bChange = false, + bDelete = false; + OUString aTmpSymbolName(m_xSymbols->get_active_text()), + aTmpSymbolSetName(m_xSymbolSets->get_active_text()); + + if (!aTmpSymbolName.isEmpty() && !aTmpSymbolSetName.isEmpty()) + { + // are all settings equal? + //! (Font-, Style- and SymbolSet name comparison is not case sensitive) + bool bEqual = m_xOrigSymbol + && aTmpSymbolSetName.equalsIgnoreAsciiCase(m_xOldSymbolSetName->get_label()) + && aTmpSymbolName == m_xOrigSymbol->GetUiName() + && m_xFonts->get_active_text().equalsIgnoreAsciiCase( + m_xOrigSymbol->GetFace().GetFamilyName()) + && m_xStyles->get_active_text().equalsIgnoreAsciiCase( + GetFontStyles().GetStyleName(m_xOrigSymbol->GetFace())) + && m_xCharsetDisplay->GetSelectCharacter() == m_xOrigSymbol->GetCharacter(); + + // only add it if there isn't already a symbol with the same name + bAdd = m_aSymbolMgrCopy.GetSymbolByUiName(aTmpSymbolName) == nullptr; + + // only delete it if all settings are equal + bDelete = bool(m_xOrigSymbol); + + // only change it if the old symbol exists and the new one is different + bChange = m_xOrigSymbol && !bEqual; + } + + m_xAddBtn->set_sensitive(bAdd); + m_xChangeBtn->set_sensitive(bChange); + m_xDeleteBtn->set_sensitive(bDelete); +} + +SmSymDefineDialog::SmSymDefineDialog(weld::Window* pParent, OutputDevice *pFntListDevice, SmSymbolManager &rMgr) + : GenericDialogController(pParent, "modules/smath/ui/symdefinedialog.ui", "EditSymbols") + , m_xVirDev(VclPtr<VirtualDevice>::Create()) + , m_rSymbolMgr(rMgr) + , m_xFontList(new FontList(pFntListDevice)) + , m_xOldSymbols(m_xBuilder->weld_combo_box("oldSymbols")) + , m_xOldSymbolSets(m_xBuilder->weld_combo_box("oldSymbolSets")) + , m_xSymbols(m_xBuilder->weld_combo_box("symbols")) + , m_xSymbolSets(m_xBuilder->weld_combo_box("symbolSets")) + , m_xFonts(m_xBuilder->weld_combo_box("fonts")) + , m_xFontsSubsetLB(m_xBuilder->weld_combo_box("fontsSubsetLB")) + , m_xStyles(m_xBuilder->weld_combo_box("styles")) + , m_xOldSymbolName(m_xBuilder->weld_label("oldSymbolName")) + , m_xOldSymbolSetName(m_xBuilder->weld_label("oldSymbolSetName")) + , m_xSymbolName(m_xBuilder->weld_label("symbolName")) + , m_xSymbolSetName(m_xBuilder->weld_label("symbolSetName")) + , m_xAddBtn(m_xBuilder->weld_button("add")) + , m_xChangeBtn(m_xBuilder->weld_button("modify")) + , m_xDeleteBtn(m_xBuilder->weld_button("delete")) + , m_xOldSymbolDisplay(new weld::CustomWeld(*m_xBuilder, "oldSymbolDisplay", m_aOldSymbolDisplay)) + , m_xSymbolDisplay(new weld::CustomWeld(*m_xBuilder, "symbolDisplay", m_aSymbolDisplay)) + , m_xCharsetDisplay(new SvxShowCharSet(m_xBuilder->weld_scrolled_window("showscroll", true), m_xVirDev)) + , m_xCharsetDisplayArea(new weld::CustomWeld(*m_xBuilder, "charsetDisplay", *m_xCharsetDisplay)) +{ + // auto completion is troublesome since that symbols character also gets automatically selected in the + // display and if the user previously selected a character to define/redefine that one this is bad + m_xOldSymbols->set_entry_completion(false); + m_xSymbols->set_entry_completion(false); + + FillFonts(); + if (m_xFonts->get_count() > 0) + SelectFont(m_xFonts->get_text(0)); + + SetSymbolSetManager(m_rSymbolMgr); + + m_xOldSymbols->connect_changed(LINK(this, SmSymDefineDialog, OldSymbolChangeHdl)); + m_xOldSymbolSets->connect_changed(LINK(this, SmSymDefineDialog, OldSymbolSetChangeHdl)); + m_xSymbolSets->connect_changed(LINK(this, SmSymDefineDialog, ModifyHdl)); + m_xOldSymbolSets->connect_changed(LINK(this, SmSymDefineDialog, ModifyHdl)); + m_xSymbols->connect_changed(LINK(this, SmSymDefineDialog, ModifyHdl)); + m_xOldSymbols->connect_changed(LINK(this, SmSymDefineDialog, ModifyHdl)); + m_xStyles->connect_changed(LINK(this, SmSymDefineDialog, ModifyHdl)); + m_xFonts->connect_changed(LINK(this, SmSymDefineDialog, FontChangeHdl)); + m_xFontsSubsetLB->connect_changed(LINK(this, SmSymDefineDialog, SubsetChangeHdl)); + m_xStyles->connect_changed(LINK(this, SmSymDefineDialog, StyleChangeHdl)); + m_xAddBtn->connect_clicked(LINK(this, SmSymDefineDialog, AddClickHdl)); + m_xChangeBtn->connect_clicked(LINK(this, SmSymDefineDialog, ChangeClickHdl)); + m_xDeleteBtn->connect_clicked(LINK(this, SmSymDefineDialog, DeleteClickHdl)); + m_xCharsetDisplay->SetHighlightHdl( LINK( this, SmSymDefineDialog, CharHighlightHdl ) ); +} + +SmSymDefineDialog::~SmSymDefineDialog() +{ +} + +short SmSymDefineDialog::run() +{ + short nResult = GenericDialogController::run(); + + // apply changes if dialog was closed by clicking OK + if (m_aSymbolMgrCopy.IsModified() && nResult == RET_OK) + m_rSymbolMgr = m_aSymbolMgrCopy; + + return nResult; +} + +void SmSymDefineDialog::SetSymbolSetManager(const SmSymbolManager &rMgr) +{ + m_aSymbolMgrCopy = rMgr; + + // Set the modified flag of the copy to false so that + // we can check later on if anything has been changed + m_aSymbolMgrCopy.SetModified(false); + + FillSymbolSets(*m_xOldSymbolSets); + if (m_xOldSymbolSets->get_count() > 0) + SelectSymbolSet(m_xOldSymbolSets->get_text(0)); + FillSymbolSets(*m_xSymbolSets); + if (m_xSymbolSets->get_count() > 0) + SelectSymbolSet(m_xSymbolSets->get_text(0)); + FillSymbols(*m_xOldSymbols); + if (m_xOldSymbols->get_count() > 0) + SelectSymbol(m_xOldSymbols->get_text(0)); + FillSymbols(*m_xSymbols); + if (m_xSymbols->get_count() > 0) + SelectSymbol(m_xSymbols->get_text(0)); + + UpdateButtons(); +} + +bool SmSymDefineDialog::SelectSymbolSet(weld::ComboBox& rComboBox, + std::u16string_view rSymbolSetName, bool bDeleteText) +{ + assert((&rComboBox == m_xOldSymbolSets.get() || &rComboBox == m_xSymbolSets.get()) && "Sm : wrong ComboBox"); + + // trim SymbolName (no leading and trailing blanks) + OUString aNormName( comphelper::string::strip(rSymbolSetName, ' ') ); + // and remove possible deviations within the input + rComboBox.set_entry_text(aNormName); + + bool bRet = false; + int nPos = rComboBox.find_text(aNormName); + + if (nPos != -1) + { + rComboBox.set_active(nPos); + bRet = true; + } + else if (bDeleteText) + rComboBox.set_entry_text(OUString()); + + bool bIsOld = &rComboBox == m_xOldSymbolSets.get(); + + // setting the SymbolSet name at the associated display + weld::Label& rFT = bIsOld ? *m_xOldSymbolSetName : *m_xSymbolSetName; + rFT.set_label(rComboBox.get_active_text()); + + // set the symbol name which belongs to the SymbolSet at the associated combobox + weld::ComboBox& rCB = bIsOld ? *m_xOldSymbols : *m_xSymbols; + FillSymbols(rCB, false); + + // display a valid respectively no symbol when changing the SymbolSets + if (bIsOld) + { + OUString aTmpOldSymbolName; + if (m_xOldSymbols->get_count() > 0) + aTmpOldSymbolName = m_xOldSymbols->get_text(0); + SelectSymbol(*m_xOldSymbols, aTmpOldSymbolName, true); + } + + UpdateButtons(); + + return bRet; +} + +void SmSymDefineDialog::SetOrigSymbol(const SmSym *pSymbol, + const OUString &rSymbolSetName) +{ + // clear old symbol + m_xOrigSymbol.reset(); + + OUString aSymName, + aSymSetName; + if (pSymbol) + { + // set new symbol + m_xOrigSymbol.reset(new SmSym(*pSymbol)); + + aSymName = pSymbol->GetUiName(); + aSymSetName = rSymbolSetName; + m_aOldSymbolDisplay.SetSymbol( pSymbol ); + } + else + { // delete displayed symbols + m_aOldSymbolDisplay.SetText(OUString()); + m_aOldSymbolDisplay.Invalidate(); + } + m_xOldSymbolName->set_label(aSymName); + m_xOldSymbolSetName->set_label(aSymSetName); +} + + +bool SmSymDefineDialog::SelectSymbol(weld::ComboBox& rComboBox, + const OUString &rSymbolName, bool bDeleteText) +{ + assert((&rComboBox == m_xOldSymbols.get() || &rComboBox == m_xSymbols.get()) && "Sm : wrong ComboBox"); + + // trim SymbolName (no blanks) + OUString aNormName = rSymbolName.replaceAll(" ", ""); + // and remove possible deviations within the input + rComboBox.set_entry_text(aNormName); + + bool bRet = false; + int nPos = rComboBox.find_text(aNormName); + + bool bIsOld = &rComboBox == m_xOldSymbols.get(); + + if (nPos != -1) + { + rComboBox.set_active(nPos); + + if (!bIsOld) + { + const SmSym *pSymbol = GetSymbol(*m_xSymbols); + if (pSymbol) + { + // choose font and style accordingly + const vcl::Font &rFont = pSymbol->GetFace(); + SelectFont(rFont.GetFamilyName(), false); + SelectStyle(GetFontStyles().GetStyleName(rFont), false); + + // Since setting the Font via the Style name of the SymbolFonts doesn't + // work really well (e.g. it can be empty even though the font itself is + // bold or italic) we're manually setting the Font with respect to the Symbol + m_xCharsetDisplay->SetFont(rFont); + m_aSymbolDisplay.SetFont(rFont); + + // select associated character + SelectChar(pSymbol->GetCharacter()); + + // since SelectChar will also set the unicode point as text in the + // symbols box, we have to set the symbol name again to get that one displayed + m_xSymbols->set_entry_text(pSymbol->GetUiName()); + } + } + + bRet = true; + } + else if (bDeleteText) + rComboBox.set_entry_text(OUString()); + + if (bIsOld) + { + // if there's a change of the old symbol, show only the available ones, otherwise show none + const SmSym *pOldSymbol = nullptr; + OUString aTmpOldSymbolSetName; + if (nPos != -1) + { + pOldSymbol = m_aSymbolMgrCopy.GetSymbolByUiName(aNormName); + aTmpOldSymbolSetName = m_xOldSymbolSets->get_active_text(); + } + SetOrigSymbol(pOldSymbol, aTmpOldSymbolSetName); + } + else + m_xSymbolName->set_label(rComboBox.get_active_text()); + + UpdateButtons(); + + return bRet; +} + + +void SmSymDefineDialog::SetFont(const OUString &rFontName, std::u16string_view rStyleName) +{ + // get Font (FontInfo) matching name and style + FontMetric aFontMetric; + if (m_xFontList) + aFontMetric = m_xFontList->Get(rFontName, WEIGHT_NORMAL, ITALIC_NONE); + SetFontStyle(rStyleName, aFontMetric); + + m_xCharsetDisplay->SetFont(aFontMetric); + m_aSymbolDisplay.SetFont(aFontMetric); + + // update subset listbox for new font's unicode subsets + FontCharMapRef xFontCharMap = m_xCharsetDisplay->GetFontCharMap(); + m_xSubsetMap.reset(new SubsetMap( xFontCharMap )); + + m_xFontsSubsetLB->clear(); + bool bFirst = true; + for (auto & subset : m_xSubsetMap->GetSubsetMap()) + { + m_xFontsSubsetLB->append(weld::toId(&subset), subset.GetName()); + // subset must live at least as long as the selected font !!! + if (bFirst) + m_xFontsSubsetLB->set_active(0); + bFirst = false; + } + if (bFirst) + m_xFontsSubsetLB->set_active(-1); + m_xFontsSubsetLB->set_sensitive(!bFirst); +} + +bool SmSymDefineDialog::SelectFont(const OUString &rFontName, bool bApplyFont) +{ + bool bRet = false; + int nPos = m_xFonts->find_text(rFontName); + + if (nPos != -1) + { + m_xFonts->set_active(nPos); + if (m_xStyles->get_count() > 0) + SelectStyle(m_xStyles->get_text(0)); + if (bApplyFont) + { + SetFont(m_xFonts->get_active_text(), m_xStyles->get_active_text()); + m_aSymbolDisplay.SetSymbol(m_xCharsetDisplay->GetSelectCharacter(), m_xCharsetDisplay->GetFont()); + } + bRet = true; + } + else + m_xFonts->set_active(-1); + FillStyles(); + + UpdateButtons(); + + return bRet; +} + + +bool SmSymDefineDialog::SelectStyle(const OUString &rStyleName, bool bApplyFont) +{ + bool bRet = false; + int nPos = m_xStyles->find_text(rStyleName); + + // if the style is not available take the first available one (if existent) + if (nPos == -1 && m_xStyles->get_count() > 0) + nPos = 0; + + if (nPos != -1) + { + m_xStyles->set_active(nPos); + if (bApplyFont) + { + SetFont(m_xFonts->get_active_text(), m_xStyles->get_active_text()); + m_aSymbolDisplay.SetSymbol(m_xCharsetDisplay->GetSelectCharacter(), m_xCharsetDisplay->GetFont()); + } + bRet = true; + } + else + m_xStyles->set_entry_text(OUString()); + + UpdateButtons(); + + return bRet; +} + +void SmSymDefineDialog::SelectChar(sal_Unicode cChar) +{ + m_xCharsetDisplay->SelectCharacter( cChar ); + m_aSymbolDisplay.SetSymbol(cChar, m_xCharsetDisplay->GetFont()); + + UpdateButtons(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/document.cxx b/starmath/source/document.cxx new file mode 100644 index 0000000000..ff28f448cb --- /dev/null +++ b/starmath/source/document.cxx @@ -0,0 +1,1583 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <com/sun/star/accessibility/AccessibleEventId.hpp> +#include <com/sun/star/uno/Any.h> + +#include <comphelper/fileformat.h> +#include <comphelper/accessibletexthelper.hxx> +#include <comphelper/string.hxx> +#include <rtl/ustrbuf.hxx> +#include <rtl/ustring.hxx> +#include <sal/log.hxx> +#include <unotools/eventcfg.hxx> +#include <sfx2/event.hxx> +#include <sfx2/app.hxx> +#include <sfx2/bindings.hxx> +#include <sfx2/docfile.hxx> +#include <sfx2/docfilt.hxx> +#include <sfx2/msg.hxx> +#include <sfx2/objface.hxx> +#include <sfx2/printer.hxx> +#include <sfx2/request.hxx> +#include <sfx2/viewfrm.hxx> +#include <comphelper/classids.hxx> +#include <sot/formats.hxx> +#include <sot/storage.hxx> +#include <svl/eitem.hxx> +#include <svl/intitem.hxx> +#include <svl/itempool.hxx> +#include <svl/slstitm.hxx> +#include <svl/hint.hxx> +#include <svl/stritem.hxx> +#include <svl/undo.hxx> +#include <svl/whiter.hxx> +#include <vcl/mapmod.hxx> +#include <vcl/virdev.hxx> +#include <tools/mapunit.hxx> +#include <vcl/settings.hxx> + +#include <document.hxx> +#include <action.hxx> +#include <dialog.hxx> +#include <format.hxx> +#include <parse.hxx> +#include <starmath.hrc> +#include <strings.hrc> +#include <smmod.hxx> +#include <symbol.hxx> +#include <unomodel.hxx> +#include <utility.hxx> +#include <view.hxx> +#include "mathtype.hxx" +#include "ooxmlexport.hxx" +#include "ooxmlimport.hxx" +#include "rtfexport.hxx" +#include <mathmlimport.hxx> +#include <mathmlexport.hxx> +#include <svx/svxids.hrc> +#include <cursor.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <visitors.hxx> +#include "accessibility.hxx" +#include <cfgitem.hxx> +#include <utility> +#include <oox/mathml/imexport.hxx> +#include <ElementsDockingWindow.hxx> +#include <smediteng.hxx> +#include <editeng/editund2.hxx> + +#define ShellClass_SmDocShell +#include <smslots.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; +using namespace ::com::sun::star::uno; + + +SFX_IMPL_SUPERCLASS_INTERFACE(SmDocShell, SfxObjectShell) + +void SmDocShell::InitInterface_Impl() +{ + GetStaticInterface()->RegisterPopupMenu("view"); +} + +void SmDocShell::SetSmSyntaxVersion(sal_Int16 nSmSyntaxVersion) +{ + mnSmSyntaxVersion = nSmSyntaxVersion; + maParser.reset(starmathdatabase::GetVersionSmParser(mnSmSyntaxVersion)); +} + +SFX_IMPL_OBJECTFACTORY(SmDocShell, SvGlobalName(SO3_SM_CLASSID), "smath" ) + +void SmDocShell::Notify(SfxBroadcaster&, const SfxHint& rHint) +{ + if (rHint.GetId() == SfxHintId::MathFormatChanged) + { + SetFormulaArranged(false); + + mnModifyCount++; //! see comment for SID_GRAPHIC_SM in SmDocShell::GetState + + Repaint(); + } +} + +void SmDocShell::LoadSymbols() +{ + SmModule *pp = SM_MOD(); + pp->GetSymbolManager().Load(); +} + + +OUString SmDocShell::GetComment() const +{ + uno::Reference<document::XDocumentPropertiesSupplier> xDPS( + GetModel(), uno::UNO_QUERY_THROW); + uno::Reference<document::XDocumentProperties> xDocProps( + xDPS->getDocumentProperties()); + return xDocProps->getDescription(); +} + + +void SmDocShell::SetText(const OUString& rBuffer) +{ + if (rBuffer == maText) + return; + + bool bIsEnabled = IsEnableSetModified(); + if( bIsEnabled ) + EnableSetModified( false ); + + maText = rBuffer; + SetFormulaArranged( false ); + + Parse(); + + SmViewShell *pViewSh = SmGetActiveView(); + if (pViewSh) + { + pViewSh->GetViewFrame().GetBindings().Invalidate(SID_TEXT); + if ( SfxObjectCreateMode::EMBEDDED == GetCreateMode() ) + { + // have SwOleClient::FormatChanged() to align the modified formula properly + // even if the visible area does not change (e.g. when formula text changes from + // "{a over b + c} over d" to "d over {a over b + c}" + SfxGetpApp()->NotifyEvent(SfxEventHint( SfxEventHintId::VisAreaChanged, GlobalEventConfig::GetEventName(GlobalEventId::VISAREACHANGED), this)); + + Repaint(); + } + else + pViewSh->GetGraphicWidget().Invalidate(); + } + + if ( bIsEnabled ) + EnableSetModified( bIsEnabled ); + SetModified(); + + // launch accessible event if necessary + SmGraphicAccessible *pAcc = pViewSh ? pViewSh->GetGraphicWidget().GetAccessible_Impl() : nullptr; + if (pAcc) + { + Any aOldValue, aNewValue; + if ( comphelper::OCommonAccessibleText::implInitTextChangedEvent( maText, rBuffer, aOldValue, aNewValue ) ) + { + pAcc->LaunchEvent( AccessibleEventId::TEXT_CHANGED, + aOldValue, aNewValue ); + } + } + + if ( GetCreateMode() == SfxObjectCreateMode::EMBEDDED ) + OnDocumentPrinterChanged(nullptr); +} + +void SmDocShell::SetFormat(SmFormat const & rFormat) +{ + maFormat = rFormat; + SetFormulaArranged( false ); + SetModified(); + + mnModifyCount++; //! see comment for SID_GRAPHIC_SM in SmDocShell::GetState + + // don't use SmGetActiveView since the view shell might not be active (0 pointer) + // if for example the Basic Macro dialog currently has the focus. Thus: + SfxViewFrame* pFrm = SfxViewFrame::GetFirst( this ); + while (pFrm) + { + pFrm->GetBindings().Invalidate(SID_GRAPHIC_SM); + pFrm = SfxViewFrame::GetNext( *pFrm, this ); + } +} + +OUString const & SmDocShell::GetAccessibleText() +{ + ArrangeFormula(); + if (maAccText.isEmpty()) + { + OSL_ENSURE( mpTree, "Tree missing" ); + if (mpTree) + { + OUStringBuffer aBuf; + mpTree->GetAccessibleText(aBuf); + maAccText = aBuf.makeStringAndClear(); + } + } + return maAccText; +} + +void SmDocShell::Parse() +{ + mpTree.reset(); + ReplaceBadChars(); + mpTree = maParser->Parse(maText); + mnModifyCount++; //! see comment for SID_GRAPHIC_SM in SmDocShell::GetState + SetFormulaArranged( false ); + InvalidateCursor(); + maUsedSymbols = maParser->GetUsedSymbols(); +} + + +void SmDocShell::ArrangeFormula() +{ + if (mbFormulaArranged) + return; + + // Only for the duration of the existence of this object the correct settings + // at the printer are guaranteed! + SmPrinterAccess aPrtAcc(*this); + OutputDevice* pOutDev = aPrtAcc.GetRefDev(); + + SAL_WARN_IF( !pOutDev, "starmath", "!! SmDocShell::ArrangeFormula: reference device missing !!"); + + // if necessary get another OutputDevice for which we format + if (!pOutDev) + { + if (SmViewShell *pView = SmGetActiveView()) + pOutDev = &pView->GetGraphicWidget().GetDrawingArea()->get_ref_device(); + else + { + pOutDev = &SM_MOD()->GetDefaultVirtualDev(); + pOutDev->SetMapMode( MapMode(SmMapUnit()) ); + } + } + OSL_ENSURE(pOutDev->GetMapMode().GetMapUnit() == SmMapUnit(), + "Sm : wrong MapMode"); + + const SmFormat &rFormat = GetFormat(); + mpTree->Prepare(rFormat, *this, 0); + + pOutDev->Push(vcl::PushFlags::TEXTLAYOUTMODE | vcl::PushFlags::TEXTLANGUAGE | + vcl::PushFlags::RTLENABLED); + + // We want the device to always be LTR, we handle RTL formulas ourselves. + pOutDev->EnableRTL(false); + + // For RTL formulas, we want the brackets to be mirrored. + bool bRTL = GetFormat().IsRightToLeft(); + pOutDev->SetLayoutMode(bRTL ? vcl::text::ComplexTextLayoutFlags::BiDiRtl + : vcl::text::ComplexTextLayoutFlags::Default); + + // Numbers should not be converted, for now. + pOutDev->SetDigitLanguage( LANGUAGE_ENGLISH ); + + mpTree->Arrange(*pOutDev, rFormat); + + pOutDev->Pop(); + + SetFormulaArranged(true); + + // invalidate accessible text + maAccText.clear(); +} + +void SmDocShell::UpdateEditEngineDefaultFonts() +{ + SmEditEngine::setSmItemPool(mpEditEngineItemPool.get(), maLinguOptions); +} + +EditEngine& SmDocShell::GetEditEngine() +{ + if (!mpEditEngine) + { + //! + //! see also SmEditWindow::DataChanged ! + //! + mpEditEngineItemPool = EditEngine::CreatePool(); + SmEditEngine::setSmItemPool(mpEditEngineItemPool.get(), maLinguOptions); + mpEditEngine.reset( new SmEditEngine( mpEditEngineItemPool.get() ) ); + mpEditEngine->EraseVirtualDevice(); + + // set initial text if the document already has some... + // (may be the case when reloading a doc) + OUString aTxt( GetText() ); + if (!aTxt.isEmpty()) + mpEditEngine->SetText( aTxt ); + mpEditEngine->ClearModifyFlag(); + } + return *mpEditEngine; +} + + +void SmDocShell::DrawFormula(OutputDevice &rDev, Point &rPosition, bool bDrawSelection) +{ + if (!mpTree) + Parse(); + OSL_ENSURE(mpTree, "Sm : NULL pointer"); + + ArrangeFormula(); + + bool bRTL = GetFormat().IsRightToLeft(); + + // Problem: What happens to WYSIWYG? While we're active inplace, we don't have a reference + // device and aren't aligned to that either. So now there can be a difference between the + // VisArea (i.e. the size within the client) and the current size. + // Idea: The difference could be adapted with SmNod::SetSize (no long-term solution) + + rPosition.AdjustX(maFormat.GetDistance( DIS_LEFTSPACE ) ); + rPosition.AdjustY(maFormat.GetDistance( DIS_TOPSPACE ) ); + + Point aPosition(rPosition); + if (bRTL && rDev.GetOutDevType() != OUTDEV_WINDOW) + aPosition.AdjustX(GetSize().Width() + - maFormat.GetDistance(DIS_LEFTSPACE) + - maFormat.GetDistance(DIS_RIGHTSPACE)); + + //! in case of high contrast-mode (accessibility option!) + //! the draw mode needs to be set to default, because when embedding + //! Math for example in Calc in "a over b" the fraction bar may not + //! be visible else. More generally: the FillColor may have been changed. + DrawModeFlags nOldDrawMode = DrawModeFlags::Default; + bool bRestoreDrawMode = false; + if (OUTDEV_WINDOW == rDev.GetOutDevType() && + rDev.GetOwnerWindow()->GetSettings().GetStyleSettings().GetHighContrastMode()) + { + nOldDrawMode = rDev.GetDrawMode(); + rDev.SetDrawMode( DrawModeFlags::Default ); + bRestoreDrawMode = true; + } + + rDev.Push(vcl::PushFlags::TEXTLAYOUTMODE | vcl::PushFlags::TEXTLANGUAGE | + vcl::PushFlags::RTLENABLED); + + // We want the device to always be LTR, we handle RTL formulas ourselves. + if (rDev.GetOutDevType() == OUTDEV_WINDOW) + rDev.EnableRTL(bRTL); + else + rDev.EnableRTL(false); + + auto nLayoutFlags = vcl::text::ComplexTextLayoutFlags::Default; + if (bRTL) + { + // For RTL formulas, we want the brackets to be mirrored. + nLayoutFlags |= vcl::text::ComplexTextLayoutFlags::BiDiRtl; + if (rDev.GetOutDevType() == OUTDEV_WINDOW) + nLayoutFlags |= vcl::text::ComplexTextLayoutFlags::TextOriginLeft; + } + + rDev.SetLayoutMode(nLayoutFlags); + + // Numbers should not be converted, for now. + rDev.SetDigitLanguage( LANGUAGE_ENGLISH ); + + //Set selection if any + if(mpCursor && bDrawSelection){ + mpCursor->AnnotateSelection(); + SmSelectionDrawingVisitor(rDev, mpTree.get(), aPosition); + } + + //Drawing using visitor + SmDrawingVisitor(rDev, aPosition, mpTree.get(), GetFormat()); + + rDev.Pop(); + + if (bRestoreDrawMode) + rDev.SetDrawMode( nOldDrawMode ); +} + +Size SmDocShell::GetSize() +{ + Size aRet; + + if (!mpTree) + Parse(); + + if (mpTree) + { + ArrangeFormula(); + aRet = mpTree->GetSize(); + + if ( !aRet.Width() || aRet.Width() == 1 ) + aRet.setWidth( 2000 ); + else + aRet.AdjustWidth(maFormat.GetDistance( DIS_LEFTSPACE ) + + maFormat.GetDistance( DIS_RIGHTSPACE ) ); + if ( !aRet.Height() ) + aRet.setHeight( 1000 ); + else + aRet.AdjustHeight(maFormat.GetDistance( DIS_TOPSPACE ) + + maFormat.GetDistance( DIS_BOTTOMSPACE ) ); + } + + return aRet; +} + +void SmDocShell::InvalidateCursor(){ + mpCursor.reset(); +} + +SmCursor& SmDocShell::GetCursor(){ + if(!mpCursor) + mpCursor.reset(new SmCursor(mpTree.get(), this)); + return *mpCursor; +} + +bool SmDocShell::HasCursor() const { return mpCursor != nullptr; } + +SmPrinterAccess::SmPrinterAccess( SmDocShell &rDocShell ) +{ + pPrinter = rDocShell.GetPrt(); + if ( pPrinter ) + { + pPrinter->Push( vcl::PushFlags::MAPMODE ); + if ( SfxObjectCreateMode::EMBEDDED == rDocShell.GetCreateMode() ) + { + // if it is an embedded object (without its own printer) + // we change the MapMode temporarily. + //!If it is a document with its own printer the MapMode should + //!be set correct (once) elsewhere(!), in order to avoid numerous + //!superfluous pushing and popping of the MapMode when using + //!this class. + + const MapUnit eOld = pPrinter->GetMapMode().GetMapUnit(); + if ( SmMapUnit() != eOld ) + { + MapMode aMap( pPrinter->GetMapMode() ); + aMap.SetMapUnit( SmMapUnit() ); + Point aTmp( aMap.GetOrigin() ); + aTmp.setX( OutputDevice::LogicToLogic( aTmp.X(), eOld, SmMapUnit() ) ); + aTmp.setY( OutputDevice::LogicToLogic( aTmp.Y(), eOld, SmMapUnit() ) ); + aMap.SetOrigin( aTmp ); + pPrinter->SetMapMode( aMap ); + } + } + } + pRefDev = rDocShell.GetRefDev(); + if ( !pRefDev || pPrinter.get() == pRefDev.get() ) + return; + + pRefDev->Push( vcl::PushFlags::MAPMODE ); + if ( SfxObjectCreateMode::EMBEDDED != rDocShell.GetCreateMode() ) + return; + + // if it is an embedded object (without its own printer) + // we change the MapMode temporarily. + //!If it is a document with its own printer the MapMode should + //!be set correct (once) elsewhere(!), in order to avoid numerous + //!superfluous pushing and popping of the MapMode when using + //!this class. + + const MapUnit eOld = pRefDev->GetMapMode().GetMapUnit(); + if ( SmMapUnit() != eOld ) + { + MapMode aMap( pRefDev->GetMapMode() ); + aMap.SetMapUnit( SmMapUnit() ); + Point aTmp( aMap.GetOrigin() ); + aTmp.setX( OutputDevice::LogicToLogic( aTmp.X(), eOld, SmMapUnit() ) ); + aTmp.setY( OutputDevice::LogicToLogic( aTmp.Y(), eOld, SmMapUnit() ) ); + aMap.SetOrigin( aTmp ); + pRefDev->SetMapMode( aMap ); + } +} + +SmPrinterAccess::~SmPrinterAccess() +{ + if ( pPrinter ) + pPrinter->Pop(); + if ( pRefDev && pRefDev != pPrinter ) + pRefDev->Pop(); +} + +Printer* SmDocShell::GetPrt() +{ + if (SfxObjectCreateMode::EMBEDDED == GetCreateMode()) + { + // Normally the server provides the printer. But if it doesn't provide one (e.g. because + // there is no connection) it still can be the case that we know the printer because it + // has been passed on by the server in OnDocumentPrinterChanged and being kept temporarily. + Printer* pPrt = GetDocumentPrinter(); + if (!pPrt && mpTmpPrinter) + pPrt = mpTmpPrinter; + return pPrt; + } + else if (!mpPrinter) + { + auto pOptions = std::make_unique<SfxItemSetFixed< + SID_PRINTTITLE, SID_PRINTZOOM, + SID_NO_RIGHT_SPACES, SID_SAVE_ONLY_USED_SYMBOLS, + SID_AUTO_CLOSE_BRACKETS, SID_SMEDITWINDOWZOOM, + SID_INLINE_EDIT_ENABLE, SID_INLINE_EDIT_ENABLE>>(GetPool()); + SmModule *pp = SM_MOD(); + pp->GetConfig()->ConfigToItemSet(*pOptions); + mpPrinter = VclPtr<SfxPrinter>::Create(std::move(pOptions)); + mpPrinter->SetMapMode(MapMode(SmMapUnit())); + } + return mpPrinter; +} + +OutputDevice* SmDocShell::GetRefDev() +{ + if (SfxObjectCreateMode::EMBEDDED == GetCreateMode()) + { + OutputDevice* pOutDev = GetDocumentRefDev(); + if (pOutDev) + return pOutDev; + } + + return GetPrt(); +} + +void SmDocShell::SetPrinter( SfxPrinter *pNew ) +{ + mpPrinter.disposeAndClear(); + mpPrinter = pNew; //Transfer ownership + mpPrinter->SetMapMode( MapMode(SmMapUnit()) ); + SetFormulaArranged(false); + Repaint(); +} + +void SmDocShell::OnDocumentPrinterChanged( Printer *pPrt ) +{ + mpTmpPrinter = pPrt; + SetFormulaArranged(false); + Size aOldSize = GetVisArea().GetSize(); + Repaint(); + if( aOldSize != GetVisArea().GetSize() && !maText.isEmpty() ) + SetModified(); + mpTmpPrinter = nullptr; +} + +void SmDocShell::Repaint() +{ + bool bIsEnabled = IsEnableSetModified(); + if (bIsEnabled) + EnableSetModified( false ); + + SetFormulaArranged(false); + + Size aVisSize = GetSize(); + SetVisAreaSize(aVisSize); + if (SmViewShell* pViewSh = SmGetActiveView()) + pViewSh->GetGraphicWidget().Invalidate(); + + if (bIsEnabled) + EnableSetModified(bIsEnabled); +} + +SmDocShell::SmDocShell( SfxModelFlags i_nSfxCreationFlags ) + : SfxObjectShell(i_nSfxCreationFlags) + , m_pMlElementTree(nullptr) + , mpPrinter(nullptr) + , mpTmpPrinter(nullptr) + , mnModifyCount(0) + , mbFormulaArranged(false) + , mnSmSyntaxVersion(SM_MOD()->GetConfig()->GetDefaultSmSyntaxVersion()) +{ + SvtLinguConfig().GetOptions(maLinguOptions); + + SetPool(&SfxGetpApp()->GetPool()); + + SmModule *pp = SM_MOD(); + maFormat = pp->GetConfig()->GetStandardFormat(); + + StartListening(maFormat); + StartListening(*pp->GetConfig()); + + SetBaseModel(new SmModel(this)); + SetSmSyntaxVersion(mnSmSyntaxVersion); + + SetMapUnit(SmMapUnit()); +} + +SmDocShell::~SmDocShell() +{ + SmModule *pp = SM_MOD(); + + EndListening(maFormat); + EndListening(*pp->GetConfig()); + + mpCursor.reset(); + mpEditEngine.reset(); + mpEditEngineItemPool.clear(); + mpPrinter.disposeAndClear(); + + mathml::SmMlIteratorFree(m_pMlElementTree); +} + +bool SmDocShell::ConvertFrom(SfxMedium &rMedium) +{ + bool bSuccess = false; + const OUString& rFltName = rMedium.GetFilter()->GetFilterName(); + + OSL_ENSURE( rFltName != STAROFFICE_XML, "Wrong filter!"); + + if ( rFltName == MATHML_XML ) + { + if (mpTree) + { + mpTree.reset(); + InvalidateCursor(); + } + rtl::Reference<SmModel> xModel(dynamic_cast<SmModel*>(GetModel().get())); + SmXMLImportWrapper aEquation(xModel); + aEquation.useHTMLMLEntities(true); + bSuccess = ( ERRCODE_NONE == aEquation.Import(rMedium) ); + } + else + { + SvStream *pStream = rMedium.GetInStream(); + if ( pStream ) + { + if ( SotStorage::IsStorageFile( pStream ) ) + { + tools::SvRef<SotStorage> aStorage = new SotStorage( pStream, false ); + if ( aStorage->IsStream("Equation Native") ) + { + // is this a MathType Storage? + OUStringBuffer aBuffer; + MathType aEquation(aBuffer); + bSuccess = aEquation.Parse( aStorage.get() ); + if ( bSuccess ) + { + maText = aBuffer.makeStringAndClear(); + Parse(); + } + } + } + } + } + + if ( GetCreateMode() == SfxObjectCreateMode::EMBEDDED ) + { + SetFormulaArranged( false ); + Repaint(); + } + + FinishedLoading(); + return bSuccess; +} + + +bool SmDocShell::InitNew( const uno::Reference < embed::XStorage >& xStorage ) +{ + bool bRet = false; + if ( SfxObjectShell::InitNew( xStorage ) ) + { + bRet = true; + SetVisArea(tools::Rectangle(Point(0, 0), Size(2000, 1000))); + } + return bRet; +} + + +bool SmDocShell::Load( SfxMedium& rMedium ) +{ + bool bRet = false; + if( SfxObjectShell::Load( rMedium )) + { + uno::Reference < embed::XStorage > xStorage = GetMedium()->GetStorage(); + if (xStorage->hasByName("content.xml") && xStorage->isStreamElement("content.xml")) + { + // is this a fabulous math package ? + rtl::Reference<SmModel> xModel(dynamic_cast<SmModel*>(GetModel().get())); + SmXMLImportWrapper aEquation(xModel); + auto nError = aEquation.Import(rMedium); + bRet = ERRCODE_NONE == nError; + SetError(nError); + } + } + + if ( GetCreateMode() == SfxObjectCreateMode::EMBEDDED ) + { + SetFormulaArranged( false ); + Repaint(); + } + + FinishedLoading(); + return bRet; +} + + +bool SmDocShell::Save() +{ + //! apply latest changes if necessary + UpdateText(); + + if ( SfxObjectShell::Save() ) + { + if (!mpTree) + Parse(); + if( mpTree ) + ArrangeFormula(); + + Reference<css::frame::XModel> xModel(GetModel()); + SmXMLExportWrapper aEquation(xModel); + aEquation.SetFlat(false); + return aEquation.Export(*GetMedium()); + } + + return false; +} + +/* + * replace bad characters that can not be saved. (#i74144) + * */ +void SmDocShell::ReplaceBadChars() +{ + bool bReplace = false; + + if (!mpEditEngine) + return; + + OUStringBuffer aBuf( mpEditEngine->GetText() ); + + for (sal_Int32 i = 0; i < aBuf.getLength(); ++i) + { + if (aBuf[i] < ' ' && aBuf[i] != '\r' && aBuf[i] != '\n' && aBuf[i] != '\t') + { + aBuf[i] = ' '; + bReplace = true; + } + } + + if (bReplace) + maText = aBuf.makeStringAndClear(); +} + + +void SmDocShell::UpdateText() +{ + if (mpEditEngine && mpEditEngine->IsModified()) + { + OUString aEngTxt( mpEditEngine->GetText() ); + if (GetText() != aEngTxt) + SetText( aEngTxt ); + } +} + + +bool SmDocShell::SaveAs( SfxMedium& rMedium ) +{ + bool bRet = false; + + //! apply latest changes if necessary + UpdateText(); + + if ( SfxObjectShell::SaveAs( rMedium ) ) + { + if (!mpTree) + Parse(); + if( mpTree ) + ArrangeFormula(); + + Reference<css::frame::XModel> xModel(GetModel()); + SmXMLExportWrapper aEquation(xModel); + aEquation.SetFlat(false); + bRet = aEquation.Export(rMedium); + } + return bRet; +} + +bool SmDocShell::ConvertTo( SfxMedium &rMedium ) +{ + bool bRet = false; + std::shared_ptr<const SfxFilter> pFlt = rMedium.GetFilter(); + if( pFlt ) + { + if( !mpTree ) + Parse(); + if( mpTree ) + ArrangeFormula(); + + const OUString& rFltName = pFlt->GetFilterName(); + if(rFltName == STAROFFICE_XML) + { + Reference<css::frame::XModel> xModel(GetModel()); + SmXMLExportWrapper aEquation(xModel); + aEquation.SetFlat(false); + bRet = aEquation.Export(rMedium); + } + else if(rFltName == MATHML_XML) + { + Reference<css::frame::XModel> xModel(GetModel()); + SmXMLExportWrapper aEquation(xModel); + aEquation.SetFlat(true); + aEquation.SetUseHTMLMLEntities(true); + bRet = aEquation.Export(rMedium); + } + else if (pFlt->GetFilterName() == "MathType 3.x") + bRet = WriteAsMathType3( rMedium ); + } + return bRet; +} + +void SmDocShell::writeFormulaOoxml( + ::sax_fastparser::FSHelperPtr const& pSerializer, + oox::core::OoxmlVersion const version, + oox::drawingml::DocumentType const documentType, + const sal_Int8 nAlign) +{ + if( !mpTree ) + Parse(); + if( mpTree ) + ArrangeFormula(); + SmOoxmlExport aEquation(mpTree.get(), version, documentType); + if(documentType == oox::drawingml::DOCUMENT_DOCX) + aEquation.ConvertFromStarMath( pSerializer, nAlign); + else + aEquation.ConvertFromStarMath(pSerializer, oox::FormulaImExportBase::eFormulaAlign::INLINE); +} + +void SmDocShell::writeFormulaRtf(OStringBuffer& rBuffer, rtl_TextEncoding nEncoding) +{ + if (!mpTree) + Parse(); + if (mpTree) + ArrangeFormula(); + SmRtfExport aEquation(mpTree.get()); + aEquation.ConvertFromStarMath(rBuffer, nEncoding); +} + +void SmDocShell::readFormulaOoxml( oox::formulaimport::XmlStream& stream ) +{ + SmOoxmlImport aEquation( stream ); + SetText( aEquation.ConvertToStarMath()); +} + +void SmDocShell::Execute(SfxRequest& rReq) +{ + switch (rReq.GetSlot()) + { + case SID_TEXTMODE: + { + SmFormat aOldFormat = GetFormat(); + SmFormat aNewFormat( aOldFormat ); + aNewFormat.SetTextmode(!aOldFormat.IsTextmode()); + + SfxUndoManager *pTmpUndoMgr = GetUndoManager(); + if (pTmpUndoMgr) + pTmpUndoMgr->AddUndoAction( + std::make_unique<SmFormatAction>(this, aOldFormat, aNewFormat)); + + SetFormat( aNewFormat ); + Repaint(); + } + break; + + case SID_AUTO_REDRAW : + { + SmModule *pp = SM_MOD(); + bool bRedraw = pp->GetConfig()->IsAutoRedraw(); + pp->GetConfig()->SetAutoRedraw(!bRedraw); + } + break; + + case SID_LOADSYMBOLS: + LoadSymbols(); + break; + + case SID_SAVESYMBOLS: + SaveSymbols(); + break; + + case SID_FONT: + { + // get device used to retrieve the FontList + OutputDevice *pDev = GetPrinter(); + if (!pDev || pDev->GetFontFaceCollectionCount() == 0) + pDev = &SM_MOD()->GetDefaultVirtualDev(); + OSL_ENSURE (pDev, "device for font list missing" ); + + SmFontTypeDialog aFontTypeDialog(rReq.GetFrameWeld(), pDev); + + SmFormat aOldFormat = GetFormat(); + aFontTypeDialog.ReadFrom( aOldFormat ); + if (aFontTypeDialog.run() == RET_OK) + { + SmFormat aNewFormat( aOldFormat ); + + aFontTypeDialog.WriteTo(aNewFormat); + SfxUndoManager *pTmpUndoMgr = GetUndoManager(); + if (pTmpUndoMgr) + pTmpUndoMgr->AddUndoAction( + std::make_unique<SmFormatAction>(this, aOldFormat, aNewFormat)); + + SetFormat( aNewFormat ); + Repaint(); + } + } + break; + + case SID_FONTSIZE: + { + SmFontSizeDialog aFontSizeDialog(rReq.GetFrameWeld()); + + SmFormat aOldFormat = GetFormat(); + aFontSizeDialog.ReadFrom( aOldFormat ); + if (aFontSizeDialog.run() == RET_OK) + { + SmFormat aNewFormat( aOldFormat ); + + aFontSizeDialog.WriteTo(aNewFormat); + + SfxUndoManager *pTmpUndoMgr = GetUndoManager(); + if (pTmpUndoMgr) + pTmpUndoMgr->AddUndoAction( + std::make_unique<SmFormatAction>(this, aOldFormat, aNewFormat)); + + SetFormat( aNewFormat ); + Repaint(); + } + } + break; + + case SID_DISTANCE: + { + SmDistanceDialog aDistanceDialog(rReq.GetFrameWeld()); + + SmFormat aOldFormat = GetFormat(); + aDistanceDialog.ReadFrom( aOldFormat ); + if (aDistanceDialog.run() == RET_OK) + { + SmFormat aNewFormat( aOldFormat ); + + aDistanceDialog.WriteTo(aNewFormat); + + SfxUndoManager *pTmpUndoMgr = GetUndoManager(); + if (pTmpUndoMgr) + pTmpUndoMgr->AddUndoAction( + std::make_unique<SmFormatAction>(this, aOldFormat, aNewFormat)); + + SetFormat( aNewFormat ); + Repaint(); + } + } + break; + + case SID_ALIGN: + { + SmAlignDialog aAlignDialog(rReq.GetFrameWeld()); + + SmFormat aOldFormat = GetFormat(); + aAlignDialog.ReadFrom( aOldFormat ); + if (aAlignDialog.run() == RET_OK) + { + SmFormat aNewFormat( aOldFormat ); + + aAlignDialog.WriteTo(aNewFormat); + + SmModule *pp = SM_MOD(); + SmFormat aFmt( pp->GetConfig()->GetStandardFormat() ); + aAlignDialog.WriteTo( aFmt ); + pp->GetConfig()->SetStandardFormat( aFmt ); + + SfxUndoManager *pTmpUndoMgr = GetUndoManager(); + if (pTmpUndoMgr) + pTmpUndoMgr->AddUndoAction( + std::make_unique<SmFormatAction>(this, aOldFormat, aNewFormat)); + + SetFormat( aNewFormat ); + Repaint(); + } + } + break; + + case SID_TEXT: + { + const SfxStringItem& rItem = rReq.GetArgs()->Get(SID_TEXT); + if (GetText() != rItem.GetValue()) + SetText(rItem.GetValue()); + } + break; + + case SID_UNDO: + case SID_REDO: + { + SfxUndoManager* pTmpUndoMgr = GetUndoManager(); + if( pTmpUndoMgr ) + { + sal_uInt16 nId = rReq.GetSlot(), nCnt = 1; + const SfxItemSet* pArgs = rReq.GetArgs(); + const SfxPoolItem* pItem; + if( pArgs && SfxItemState::SET == pArgs->GetItemState( nId, false, &pItem )) + nCnt = static_cast<const SfxUInt16Item*>(pItem)->GetValue(); + + bool (SfxUndoManager::*fnDo)(); + + size_t nCount; + if( SID_UNDO == rReq.GetSlot() ) + { + nCount = pTmpUndoMgr->GetUndoActionCount(); + fnDo = &SfxUndoManager::Undo; + } + else + { + nCount = pTmpUndoMgr->GetRedoActionCount(); + fnDo = &SfxUndoManager::Redo; + } + + try + { + for( ; nCnt && nCount; --nCnt, --nCount ) + (pTmpUndoMgr->*fnDo)(); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("starmath"); + } + } + Repaint(); + UpdateText(); + SfxViewFrame* pFrm = SfxViewFrame::GetFirst( this ); + while( pFrm ) + { + SfxBindings& rBind = pFrm->GetBindings(); + rBind.Invalidate(SID_UNDO); + rBind.Invalidate(SID_REDO); + rBind.Invalidate(SID_REPEAT); + rBind.Invalidate(SID_CLEARHISTORY); + pFrm = SfxViewFrame::GetNext( *pFrm, this ); + } + } + break; + } + + rReq.Done(); +} + + +void SmDocShell::GetState(SfxItemSet &rSet) +{ + SfxWhichIter aIter(rSet); + + for (sal_uInt16 nWh = aIter.FirstWhich(); 0 != nWh; nWh = aIter.NextWhich()) + { + switch (nWh) + { + case SID_TEXTMODE: + rSet.Put(SfxBoolItem(SID_TEXTMODE, GetFormat().IsTextmode())); + break; + + case SID_DOCTEMPLATE : + rSet.DisableItem(SID_DOCTEMPLATE); + break; + + case SID_AUTO_REDRAW : + { + SmModule *pp = SM_MOD(); + bool bRedraw = pp->GetConfig()->IsAutoRedraw(); + + rSet.Put(SfxBoolItem(SID_AUTO_REDRAW, bRedraw)); + } + break; + + case SID_MODIFYSTATUS: + { + sal_Unicode cMod = ' '; + if (IsModified()) + cMod = '*'; + rSet.Put(SfxStringItem(SID_MODIFYSTATUS, OUString(cMod))); + } + break; + + case SID_TEXT: + rSet.Put(SfxStringItem(SID_TEXT, GetText())); + break; + + case SID_GRAPHIC_SM: + //! very old (pre UNO) and ugly hack to invalidate the SmGraphicWidget. + //! If mnModifyCount gets changed then the call below will implicitly notify + //! SmGraphicController::StateChanged and there the window gets invalidated. + //! Thus all the 'mnModifyCount++' before invalidating this slot. + rSet.Put(SfxInt16Item(SID_GRAPHIC_SM, mnModifyCount)); + break; + + case SID_UNDO: + case SID_REDO: + { + SfxViewFrame* pFrm = SfxViewFrame::GetFirst( this ); + if( pFrm ) + pFrm->GetSlotState( nWh, nullptr, &rSet ); + else + rSet.DisableItem( nWh ); + } + break; + + case SID_GETUNDOSTRINGS: + case SID_GETREDOSTRINGS: + { + SfxUndoManager* pTmpUndoMgr = GetUndoManager(); + if( pTmpUndoMgr ) + { + OUString(SfxUndoManager::*fnGetComment)( size_t, bool const ) const; + + size_t nCount; + if( SID_GETUNDOSTRINGS == nWh ) + { + nCount = pTmpUndoMgr->GetUndoActionCount(); + fnGetComment = &SfxUndoManager::GetUndoActionComment; + } + else + { + nCount = pTmpUndoMgr->GetRedoActionCount(); + fnGetComment = &SfxUndoManager::GetRedoActionComment; + } + if (nCount) + { + OUStringBuffer aBuf; + for (size_t n = 0; n < nCount; ++n) + { + aBuf.append((pTmpUndoMgr->*fnGetComment)( n, SfxUndoManager::TopLevel )); + aBuf.append('\n'); + } + + SfxStringListItem aItem( nWh ); + aItem.SetString( aBuf.makeStringAndClear() ); + rSet.Put( aItem ); + } + } + else + rSet.DisableItem( nWh ); + } + break; + } + } +} + + +SfxUndoManager *SmDocShell::GetUndoManager() +{ + if (!mpEditEngine) + GetEditEngine(); + return &mpEditEngine->GetUndoManager(); +} + + +void SmDocShell::SaveSymbols() +{ + SmModule *pp = SM_MOD(); + pp->GetSymbolManager().Save(); +} + + +void SmDocShell::Draw(OutputDevice *pDevice, + const JobSetup &, + sal_uInt16 /*nAspect*/, + bool /*bOutputForScreen*/) +{ + pDevice->IntersectClipRegion(GetVisArea()); + Point atmppoint; + DrawFormula(*pDevice, atmppoint); +} + +SfxItemPool& SmDocShell::GetPool() +{ + return SfxGetpApp()->GetPool(); +} + +void SmDocShell::SetVisArea(const tools::Rectangle & rVisArea) +{ + tools::Rectangle aNewRect(rVisArea); + + aNewRect.SetPos(Point()); + + if (aNewRect.IsWidthEmpty()) + aNewRect.SetRight( 2000 ); + if (aNewRect.IsHeightEmpty()) + aNewRect.SetBottom( 1000 ); + + bool bIsEnabled = IsEnableSetModified(); + if ( bIsEnabled ) + EnableSetModified( false ); + + //TODO/LATER: it's unclear how this interacts with the SFX code + // If outplace editing, then don't resize the OutplaceWindow. But the + // ObjectShell has to resize. + bool bUnLockFrame; + if( GetCreateMode() == SfxObjectCreateMode::EMBEDDED && !IsInPlaceActive() && GetFrame() ) + { + GetFrame()->LockAdjustPosSizePixel(); + bUnLockFrame = true; + } + else + bUnLockFrame = false; + + SfxObjectShell::SetVisArea( aNewRect ); + + if( bUnLockFrame ) + GetFrame()->UnlockAdjustPosSizePixel(); + + if ( bIsEnabled ) + EnableSetModified( bIsEnabled ); +} + + +void SmDocShell::FillClass(SvGlobalName* pClassName, + SotClipboardFormatId* pFormat, + OUString* pFullTypeName, + sal_Int32 nFileFormat, + bool bTemplate /* = false */) const +{ + if (nFileFormat == SOFFICE_FILEFORMAT_60 ) + { + *pClassName = SvGlobalName(SO3_SM_CLASSID_60); + *pFormat = SotClipboardFormatId::STARMATH_60; + *pFullTypeName = SmResId(STR_MATH_DOCUMENT_FULLTYPE_CURRENT); + } + else if (nFileFormat == SOFFICE_FILEFORMAT_8 ) + { + *pClassName = SvGlobalName(SO3_SM_CLASSID_60); + *pFormat = bTemplate ? SotClipboardFormatId::STARMATH_8_TEMPLATE : SotClipboardFormatId::STARMATH_8; + *pFullTypeName = SmResId(STR_MATH_DOCUMENT_FULLTYPE_CURRENT); + } +} + +void SmDocShell::SetModified(bool bModified) +{ + if( IsEnableSetModified() ) + { + SfxObjectShell::SetModified( bModified ); + Broadcast(SfxHint(SfxHintId::DocChanged)); + } +} + +bool SmDocShell::WriteAsMathType3( SfxMedium& rMedium ) +{ + OUStringBuffer aTextAsBuffer(maText); + MathType aEquation(aTextAsBuffer, mpTree.get()); + return aEquation.ConvertFromStarMath( rMedium ); +} + +void SmDocShell::SetRightToLeft(bool bRTL) +{ + SmFormat aOldFormat = GetFormat(); + if (aOldFormat.IsRightToLeft() == bRTL) + return; + + SmFormat aNewFormat(aOldFormat); + aNewFormat.SetRightToLeft(bRTL); + + SfxUndoManager* pTmpUndoMgr = GetUndoManager(); + if (pTmpUndoMgr) + pTmpUndoMgr->AddUndoAction( + std::make_unique<SmFormatAction>(this, aOldFormat, aNewFormat)); + + SetFormat(aNewFormat); + Repaint(); +} + +static Size GetTextLineSize(OutputDevice const& rDevice, const OUString& rLine) +{ + Size aSize(rDevice.GetTextWidth(rLine), rDevice.GetTextHeight()); + const tools::Long nTabPos = rLine.isEmpty() ? 0 : rDevice.approximate_digit_width() * 8; + + if (nTabPos) + { + aSize.setWidth(0); + sal_Int32 nPos = 0; + do + { + if (nPos > 0) + aSize.setWidth(((aSize.Width() / nTabPos) + 1) * nTabPos); + + const OUString aText = rLine.getToken(0, '\t', nPos); + aSize.AdjustWidth(rDevice.GetTextWidth(aText)); + } while (nPos >= 0); + } + + return aSize; +} + +static Size GetTextSize(OutputDevice const& rDevice, std::u16string_view rText, + tools::Long MaxWidth) +{ + Size aSize; + Size aTextSize; + if (rText.empty()) + return aTextSize; + + sal_Int32 nPos = 0; + do + { + OUString aLine(o3tl::getToken(rText, 0, '\n', nPos)); + aLine = aLine.replaceAll("\r", ""); + + aSize = GetTextLineSize(rDevice, aLine); + + if (aSize.Width() > MaxWidth) + { + do + { + OUString aText; + sal_Int32 m = aLine.getLength(); + sal_Int32 nLen = m; + + for (sal_Int32 n = 0; n < nLen; n++) + { + sal_Unicode cLineChar = aLine[n]; + if ((cLineChar == ' ') || (cLineChar == '\t')) + { + aText = aLine.copy(0, n); + if (GetTextLineSize(rDevice, aText).Width() < MaxWidth) + m = n; + else + break; + } + } + + aText = aLine.copy(0, m); + aLine = aLine.replaceAt(0, m, u""); + aSize = GetTextLineSize(rDevice, aText); + aTextSize.AdjustHeight(aSize.Height()); + aTextSize.setWidth(std::clamp(aSize.Width(), aTextSize.Width(), MaxWidth)); + + aLine = comphelper::string::stripStart(aLine, ' '); + aLine = comphelper::string::stripStart(aLine, '\t'); + aLine = comphelper::string::stripStart(aLine, ' '); + } while (!aLine.isEmpty()); + } + else + { + aTextSize.AdjustHeight(aSize.Height()); + aTextSize.setWidth(std::max(aTextSize.Width(), aSize.Width())); + } + } while (nPos >= 0); + + return aTextSize; +} + +static void DrawTextLine(OutputDevice& rDevice, const Point& rPosition, const OUString& rLine) +{ + Point aPoint(rPosition); + const tools::Long nTabPos = rLine.isEmpty() ? 0 : rDevice.approximate_digit_width() * 8; + + if (nTabPos) + { + sal_Int32 nPos = 0; + do + { + if (nPos > 0) + aPoint.setX(((aPoint.X() / nTabPos) + 1) * nTabPos); + + OUString aText = rLine.getToken(0, '\t', nPos); + rDevice.DrawText(aPoint, aText); + aPoint.AdjustX(rDevice.GetTextWidth(aText)); + } while (nPos >= 0); + } + else + rDevice.DrawText(aPoint, rLine); +} + +static void DrawText(OutputDevice& rDevice, const Point& rPosition, std::u16string_view rText, + sal_uInt16 MaxWidth) +{ + if (rText.empty()) + return; + + Point aPoint(rPosition); + Size aSize; + + sal_Int32 nPos = 0; + do + { + OUString aLine(o3tl::getToken(rText, 0, '\n', nPos)); + aLine = aLine.replaceAll("\r", ""); + aSize = GetTextLineSize(rDevice, aLine); + if (aSize.Width() > MaxWidth) + { + do + { + OUString aText; + sal_Int32 m = aLine.getLength(); + sal_Int32 nLen = m; + + for (sal_Int32 n = 0; n < nLen; n++) + { + sal_Unicode cLineChar = aLine[n]; + if ((cLineChar == ' ') || (cLineChar == '\t')) + { + aText = aLine.copy(0, n); + if (GetTextLineSize(rDevice, aText).Width() < MaxWidth) + m = n; + else + break; + } + } + aText = aLine.copy(0, m); + aLine = aLine.replaceAt(0, m, u""); + + DrawTextLine(rDevice, aPoint, aText); + aPoint.AdjustY(aSize.Height()); + + aLine = comphelper::string::stripStart(aLine, ' '); + aLine = comphelper::string::stripStart(aLine, '\t'); + aLine = comphelper::string::stripStart(aLine, ' '); + } while (GetTextLineSize(rDevice, aLine).Width() > MaxWidth); + + // print the remaining text + if (!aLine.isEmpty()) + { + DrawTextLine(rDevice, aPoint, aLine); + aPoint.AdjustY(aSize.Height()); + } + } + else + { + DrawTextLine(rDevice, aPoint, aLine); + aPoint.AdjustY(aSize.Height()); + } + } while (nPos >= 0); +} + +void SmDocShell::Impl_Print(OutputDevice& rOutDev, const SmPrintUIOptions& rPrintUIOptions, + tools::Rectangle aOutRect) +{ + const bool bIsPrintTitle = rPrintUIOptions.getBoolValue(PRTUIOPT_TITLE_ROW, true); + const bool bIsPrintFrame = rPrintUIOptions.getBoolValue(PRTUIOPT_BORDER, true); + const bool bIsPrintFormulaText = rPrintUIOptions.getBoolValue(PRTUIOPT_FORMULA_TEXT, true); + SmPrintSize ePrintSize(static_cast<SmPrintSize>( + rPrintUIOptions.getIntValue(PRTUIOPT_PRINT_FORMAT, PRINT_SIZE_NORMAL))); + const sal_uInt16 nZoomFactor + = static_cast<sal_uInt16>(rPrintUIOptions.getIntValue(PRTUIOPT_PRINT_SCALE, 100)); + + rOutDev.Push(); + rOutDev.SetLineColor(COL_BLACK); + + // output text on top + if (bIsPrintTitle) + { + Size aSize600(0, 600); + Size aSize650(0, 650); + vcl::Font aFont(FAMILY_DONTKNOW, aSize600); + + aFont.SetAlignment(ALIGN_TOP); + aFont.SetWeight(WEIGHT_BOLD); + aFont.SetFontSize(aSize650); + aFont.SetColor(COL_BLACK); + rOutDev.SetFont(aFont); + + Size aTitleSize(GetTextSize(rOutDev, GetTitle(), aOutRect.GetWidth() - 200)); + + aFont.SetWeight(WEIGHT_NORMAL); + aFont.SetFontSize(aSize600); + rOutDev.SetFont(aFont); + + Size aDescSize(GetTextSize(rOutDev, GetComment(), aOutRect.GetWidth() - 200)); + + if (bIsPrintFrame) + rOutDev.DrawRect(tools::Rectangle( + aOutRect.TopLeft(), Size(aOutRect.GetWidth(), 100 + aTitleSize.Height() + 200 + + aDescSize.Height() + 100))); + aOutRect.AdjustTop(200); + + // output title + aFont.SetWeight(WEIGHT_BOLD); + aFont.SetFontSize(aSize650); + rOutDev.SetFont(aFont); + Point aPoint(aOutRect.Left() + (aOutRect.GetWidth() - aTitleSize.Width()) / 2, + aOutRect.Top()); + DrawText(rOutDev, aPoint, GetTitle(), + sal::static_int_cast<sal_uInt16>(aOutRect.GetWidth() - 200)); + aOutRect.AdjustTop(aTitleSize.Height() + 200); + + // output description + aFont.SetWeight(WEIGHT_NORMAL); + aFont.SetFontSize(aSize600); + rOutDev.SetFont(aFont); + aPoint.setX(aOutRect.Left() + (aOutRect.GetWidth() - aDescSize.Width()) / 2); + aPoint.setY(aOutRect.Top()); + DrawText(rOutDev, aPoint, GetComment(), + sal::static_int_cast<sal_uInt16>(aOutRect.GetWidth() - 200)); + aOutRect.AdjustTop(aDescSize.Height() + 300); + } + + // output text on bottom + if (bIsPrintFormulaText) + { + vcl::Font aFont(FAMILY_DONTKNOW, Size(0, 600)); + aFont.SetAlignment(ALIGN_TOP); + aFont.SetColor(COL_BLACK); + + // get size + rOutDev.SetFont(aFont); + + Size aSize(GetTextSize(rOutDev, GetText(), aOutRect.GetWidth() - 200)); + + aOutRect.AdjustBottom(-(aSize.Height() + 600)); + + if (bIsPrintFrame) + rOutDev.DrawRect(tools::Rectangle( + aOutRect.BottomLeft(), Size(aOutRect.GetWidth(), 200 + aSize.Height() + 200))); + + Point aPoint(aOutRect.Left() + (aOutRect.GetWidth() - aSize.Width()) / 2, + aOutRect.Bottom() + 300); + DrawText(rOutDev, aPoint, GetText(), + sal::static_int_cast<sal_uInt16>(aOutRect.GetWidth() - 200)); + aOutRect.AdjustBottom(-200); + } + + if (bIsPrintFrame) + rOutDev.DrawRect(aOutRect); + + aOutRect.AdjustTop(100); + aOutRect.AdjustLeft(100); + aOutRect.AdjustBottom(-100); + aOutRect.AdjustRight(-100); + + Size aSize(GetSize()); + + MapMode OutputMapMode; + switch (ePrintSize) + { + case PRINT_SIZE_NORMAL: + OutputMapMode = MapMode(SmMapUnit()); + break; + + case PRINT_SIZE_SCALED: + if (!aSize.IsEmpty()) + { + sal_uInt16 nZ + = std::min(o3tl::convert(aOutRect.GetWidth(), 100, aSize.Width()), + o3tl::convert(aOutRect.GetHeight(), 100, aSize.Height())); + if (bIsPrintFrame && nZ > MINZOOM) + nZ -= 10; + Fraction aFraction(std::clamp(nZ, MINZOOM, MAXZOOM), 100); + + OutputMapMode = MapMode(SmMapUnit(), Point(), aFraction, aFraction); + } + else + OutputMapMode = MapMode(SmMapUnit()); + break; + + case PRINT_SIZE_ZOOMED: + { + Fraction aFraction(nZoomFactor, 100); + + OutputMapMode = MapMode(SmMapUnit(), Point(), aFraction, aFraction); + break; + } + } + + aSize = OutputDevice::LogicToLogic(aSize, OutputMapMode, MapMode(SmMapUnit())); + + Point aPos(aOutRect.Left() + (aOutRect.GetWidth() - aSize.Width()) / 2, + aOutRect.Top() + (aOutRect.GetHeight() - aSize.Height()) / 2); + + aPos = OutputDevice::LogicToLogic(aPos, MapMode(SmMapUnit()), OutputMapMode); + aOutRect = OutputDevice::LogicToLogic(aOutRect, MapMode(SmMapUnit()), OutputMapMode); + + rOutDev.SetMapMode(OutputMapMode); + rOutDev.SetClipRegion(vcl::Region(aOutRect)); + DrawFormula(rOutDev, aPos); + rOutDev.SetClipRegion(); + + rOutDev.Pop(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/edit.cxx b/starmath/source/edit.cxx new file mode 100644 index 0000000000..bc1138a18d --- /dev/null +++ b/starmath/source/edit.cxx @@ -0,0 +1,863 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <starmath.hrc> +#include <helpids.h> + +#include <vcl/commandevent.hxx> +#include <vcl/event.hxx> +#include <vcl/ptrstyle.hxx> +#include <vcl/settings.hxx> + +#include <editeng/editview.hxx> +#include <editeng/editeng.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/sfxsids.hrc> +#include <svl/stritem.hxx> +#include <svl/itemset.hxx> +#include <sfx2/viewfrm.hxx> +#include <osl/diagnose.h> +#include <o3tl/string_view.hxx> + +#include <edit.hxx> +#include <smmod.hxx> +#include <view.hxx> +#include <document.hxx> +#include <cfgitem.hxx> +#include <smediteng.hxx> + +using namespace com::sun::star::accessibility; +using namespace com::sun::star; + + +void SmGetLeftSelectionPart(const ESelection &rSel, + sal_Int32 &nPara, sal_uInt16 &nPos) + // returns paragraph number and position of the selections left part +{ + // compare start and end of selection and use the one that comes first + if ( rSel.nStartPara < rSel.nEndPara + || (rSel.nStartPara == rSel.nEndPara && rSel.nStartPos < rSel.nEndPos) ) + { nPara = rSel.nStartPara; + nPos = rSel.nStartPos; + } + else + { nPara = rSel.nEndPara; + nPos = rSel.nEndPos; + } +} + +SmEditTextWindow::SmEditTextWindow(SmEditWindow& rEditWindow) + : mrEditWindow(rEditWindow) + , aModifyIdle("SmEditWindow ModifyIdle") + , aCursorMoveIdle("SmEditWindow CursorMoveIdle") +{ + SetAcceptsTab(true); + + aModifyIdle.SetInvokeHandler(LINK(this, SmEditTextWindow, ModifyTimerHdl)); + aModifyIdle.SetPriority(TaskPriority::LOWEST); + + if (!SmViewShell::IsInlineEditEnabled()) + { + aCursorMoveIdle.SetInvokeHandler(LINK(this, SmEditTextWindow, CursorMoveTimerHdl)); + aCursorMoveIdle.SetPriority(TaskPriority::LOWEST); + } +} + +SmEditTextWindow::~SmEditTextWindow() +{ + aModifyIdle.Stop(); + StartCursorMove(); +} + +EditEngine* SmEditTextWindow::GetEditEngine() const +{ + SmDocShell *pDoc = mrEditWindow.GetDoc(); + assert(pDoc); + return &pDoc->GetEditEngine(); +} + +void SmEditTextWindow::EditViewScrollStateChange() +{ + mrEditWindow.SetScrollBarRanges(); +} + +void SmEditTextWindow::SetDrawingArea(weld::DrawingArea* pDrawingArea) +{ + weld::CustomWidgetController::SetDrawingArea(pDrawingArea); + + const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); + Color aBgColor = rStyleSettings.GetWindowColor(); + + OutputDevice& rDevice = pDrawingArea->get_ref_device(); + rDevice.SetBackground(aBgColor); + + SetHelpId(HID_SMA_COMMAND_WIN_EDIT); + + EnableRTL(false); + + EditEngine* pEditEngine = GetEditEngine(); + + m_xEditView.reset(new EditView(pEditEngine, nullptr)); + m_xEditView->setEditViewCallbacks(this); + + pEditEngine->InsertView(m_xEditView.get()); + + m_xEditView->SetOutputArea(mrEditWindow.AdjustScrollBars()); + + m_xEditView->SetBackgroundColor(aBgColor); + + pDrawingArea->set_cursor(PointerStyle::Text); + + pEditEngine->SetStatusEventHdl(LINK(this, SmEditTextWindow, EditStatusHdl)); + + InitAccessible(); + + //Apply zoom to smeditwindow text + if(GetEditView()) + static_cast<SmEditEngine*>(GetEditEngine())->executeZoom(GetEditView()); +} + +SmEditWindow::SmEditWindow(SmCmdBoxWindow &rMyCmdBoxWin, weld::Builder& rBuilder) + : rCmdBox(rMyCmdBoxWin) + , mxScrolledWindow(rBuilder.weld_scrolled_window("scrolledwindow", true)) +{ + mxScrolledWindow->connect_vadjustment_changed(LINK(this, SmEditWindow, ScrollHdl)); + + CreateEditView(rBuilder); +} + +SmEditWindow::~SmEditWindow() COVERITY_NOEXCEPT_FALSE +{ + DeleteEditView(); + mxScrolledWindow.reset(); +} + +weld::Window* SmEditWindow::GetFrameWeld() const +{ + return rCmdBox.GetFrameWeld(); +} + +void SmEditTextWindow::StartCursorMove() +{ + if (!SmViewShell::IsInlineEditEnabled()) + aCursorMoveIdle.Stop(); +} + +void SmEditWindow::InvalidateSlots() +{ + GetView()->InvalidateSlots(); +} + +SmViewShell * SmEditWindow::GetView() +{ + return rCmdBox.GetView(); +} + +SmDocShell * SmEditWindow::GetDoc() +{ + SmViewShell *pView = rCmdBox.GetView(); + return pView ? pView->GetDoc() : nullptr; +} + +EditView * SmEditWindow::GetEditView() const +{ + return mxTextControl ? mxTextControl->GetEditView() : nullptr; +} + +EditEngine * SmEditWindow::GetEditEngine() +{ + if (SmDocShell *pDoc = GetDoc()) + return &pDoc->GetEditEngine(); + return nullptr; +} + +void SmEditTextWindow::StyleUpdated() +{ + WeldEditView::StyleUpdated(); + EditEngine *pEditEngine = GetEditEngine(); + SmDocShell *pDoc = mrEditWindow.GetDoc(); + + if (pEditEngine && pDoc) + { + //! + //! see also SmDocShell::GetEditEngine() ! + //! + const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); + + pDoc->UpdateEditEngineDefaultFonts(); + pEditEngine->SetBackgroundColor(rStyleSettings.GetFieldColor()); + pEditEngine->SetDefTab(sal_uInt16(GetTextWidth("XXXX"))); + + // forces new settings to be used + // unfortunately this resets the whole edit engine + // thus we need to save at least the text + OUString aTxt( pEditEngine->GetText() ); + pEditEngine->Clear(); //incorrect font size + pEditEngine->SetText( aTxt ); + + Resize(); + } + + // Apply zoom to smeditwindow text + static_cast<SmEditEngine*>(GetEditEngine())->executeZoom(GetEditView()); +} + +IMPL_LINK_NOARG(SmEditTextWindow, ModifyTimerHdl, Timer *, void) +{ + UpdateStatus(false); + aModifyIdle.Stop(); +} + +IMPL_LINK_NOARG(SmEditTextWindow, CursorMoveTimerHdl, Timer *, void) + // every once in a while check cursor position (selection) of edit + // window and if it has changed (try to) set the formula-cursor + // according to that. +{ + if (SmViewShell::IsInlineEditEnabled()) + return; + + ESelection aNewSelection(GetSelection()); + + if (aNewSelection != aOldSelection) + { + if (SmViewShell *pViewSh = mrEditWindow.GetView()) + { + // get row and column to look for + sal_Int32 nRow; + sal_uInt16 nCol; + SmGetLeftSelectionPart(aNewSelection, nRow, nCol); + pViewSh->GetGraphicWidget().SetCursorPos(static_cast<sal_uInt16>(nRow), nCol); + aOldSelection = aNewSelection; + } + } + aCursorMoveIdle.Stop(); +} + +bool SmEditTextWindow::MouseButtonUp(const MouseEvent &rEvt) +{ + bool bRet = WeldEditView::MouseButtonUp(rEvt); + if (!SmViewShell::IsInlineEditEnabled()) + CursorMoveTimerHdl(&aCursorMoveIdle); + mrEditWindow.InvalidateSlots(); + return bRet; +} + +bool SmEditTextWindow::Command(const CommandEvent& rCEvt) +{ + // no zooming in Command window + const CommandWheelData* pWData = rCEvt.GetWheelData(); + if (pWData && CommandWheelMode::ZOOM == pWData->GetMode()) + return true; + + //pass alt press/release to parent impl + if (rCEvt.GetCommand() == CommandEventId::ModKeyChange) + return false; + + if (rCEvt.GetCommand() == CommandEventId::ContextMenu) + { + ReleaseMouse(); + SmCmdBoxWindow& rCmdBox = mrEditWindow.GetCmdBox(); + rCmdBox.ShowContextMenu(rCmdBox.WidgetToWindowPos(*GetDrawingArea(), rCEvt.GetMousePosPixel())); + GrabFocus(); + return true; + } + + bool bConsumed = WeldEditView::Command(rCEvt); + if (bConsumed) + UserPossiblyChangedText(); + return bConsumed; +} + +bool SmEditTextWindow::KeyInput(const KeyEvent& rKEvt) +{ + if (rKEvt.GetKeyCode().GetCode() == KEY_F1) + { + mrEditWindow.GetView()->StartMainHelp(); + return true; + } + + if (rKEvt.GetKeyCode().GetCode() == KEY_ESCAPE) + { + bool bCallBase = true; + SfxViewShell* pViewShell = mrEditWindow.GetView(); + if ( dynamic_cast<const SmViewShell *>(pViewShell) ) + { + // Terminate possible InPlace mode + bCallBase = !pViewShell->Escape(); + } + return !bCallBase; + } + + StartCursorMove(); + + bool autoClose = false; + EditView* pEditView = GetEditView(); + ESelection aSelection = pEditView->GetSelection(); + // as we don't support RTL in Math, we need to swap values from selection when they were done + // in RTL form + aSelection.Adjust(); + OUString selected = pEditView->GetEditEngine()->GetText(aSelection); + + // Check is auto close brackets/braces is disabled + SmModule *pMod = SM_MOD(); + if (pMod && !pMod->GetConfig()->IsAutoCloseBrackets()) + autoClose = false; + else if (o3tl::trim(selected) == u"<?>") + autoClose = true; + else if (selected.isEmpty() && !aSelection.HasRange()) + { + selected = pEditView->GetEditEngine()->GetText(aSelection.nEndPara); + if (!selected.isEmpty()) + { + sal_Int32 index = selected.indexOf("\n", aSelection.nEndPos); + if (index != -1) + { + selected = selected.copy(index, sal_Int32(aSelection.nEndPos-index)); + if (o3tl::trim(selected).empty()) + autoClose = true; + } + else + { + sal_Int32 length = selected.getLength(); + if (aSelection.nEndPos == length) + autoClose = true; + else + { + selected = selected.copy(aSelection.nEndPos); + if (o3tl::trim(selected).empty()) + autoClose = true; + } + } + } + else + autoClose = true; + } + + bool bConsumed = WeldEditView::KeyInput(rKEvt); + if (!bConsumed) + { + SmViewShell *pView = mrEditWindow.GetView(); + if (pView) + bConsumed = pView->KeyInput(rKEvt); + if (pView && !bConsumed) + { + // F1 (help) leads to the destruction of this + Flush(); + if ( aModifyIdle.IsActive() ) + aModifyIdle.Stop(); + } + else + { + // SFX has maybe called a slot of the view and thus (because of a hack in SFX) + // set the focus to the view + SmViewShell* pVShell = mrEditWindow.GetView(); + if ( pVShell && pVShell->GetGraphicWidget().HasFocus() ) + { + GrabFocus(); + } + } + } + else + { + UserPossiblyChangedText(); + } + + // get the current char of the key event + sal_Unicode cCharCode = rKEvt.GetCharCode(); + OUString sClose; + + if (cCharCode == '{') + sClose = " }"; + else if (cCharCode == '[') + sClose = " ]"; + else if (cCharCode == '(') + sClose = " )"; + + // auto close the current character only when needed + if (!sClose.isEmpty() && autoClose) + { + pEditView->InsertText(sClose); + // position it at center of brackets + aSelection.nStartPos += 2; + aSelection.nEndPos = aSelection.nStartPos; + pEditView->SetSelection(aSelection); + } + + mrEditWindow.InvalidateSlots(); + return bConsumed; +} + +void SmEditTextWindow::UserPossiblyChangedText() +{ + // have doc-shell modified only for formula input/change and not + // cursor travelling and such things... + SmDocShell *pDocShell = mrEditWindow.GetDoc(); + EditEngine *pEditEngine = GetEditEngine(); + if (pDocShell && pEditEngine && pEditEngine->IsModified()) + pDocShell->SetModified(true); + aModifyIdle.Start(); +} + +void SmEditWindow::CreateEditView(weld::Builder& rBuilder) +{ + assert(!mxTextControl); + + EditEngine *pEditEngine = GetEditEngine(); + //! pEditEngine may be 0. + //! For example when the program is used by the document-converter + if (!pEditEngine) + return; + + mxTextControl.reset(new SmEditTextWindow(*this)); + mxTextControlWin.reset(new weld::CustomWeld(rBuilder, "editview", *mxTextControl)); + + SetScrollBarRanges(); +} + +IMPL_LINK_NOARG(SmEditTextWindow, EditStatusHdl, EditStatus&, void) +{ + Resize(); +} + +IMPL_LINK(SmEditWindow, ScrollHdl, weld::ScrolledWindow&, rScrolledWindow, void) +{ + if (EditView* pEditView = GetEditView()) + { + pEditView->SetVisArea(tools::Rectangle( + Point(0, + rScrolledWindow.vadjustment_get_value()), + pEditView->GetVisArea().GetSize())); + pEditView->Invalidate(); + } +} + +tools::Rectangle SmEditWindow::AdjustScrollBars() +{ + tools::Rectangle aRect(Point(), rCmdBox.GetOutputSizePixel()); + + if (mxScrolledWindow) + { + const auto nScrollSize = mxScrolledWindow->get_scroll_thickness(); + const auto nMargin = nScrollSize + 2; + aRect.AdjustRight(-nMargin); + aRect.AdjustBottom(-nMargin); + } + + return aRect; +} + +void SmEditWindow::SetScrollBarRanges() +{ + EditEngine *pEditEngine = GetEditEngine(); + if (!pEditEngine) + return; + if (!mxScrolledWindow) + return; + EditView* pEditView = GetEditView(); + if (!pEditView) + return; + + int nVUpper = pEditEngine->GetTextHeight(); + int nVCurrentDocPos = pEditView->GetVisArea().Top(); + const Size aOut(pEditView->GetOutputArea().GetSize()); + int nVStepIncrement = aOut.Height() * 2 / 10; + int nVPageIncrement = aOut.Height() * 8 / 10; + int nVPageSize = aOut.Height(); + + /* limit the page size to below nUpper because gtk's gtk_scrolled_window_start_deceleration has + effectively... + + lower = gtk_adjustment_get_lower + upper = gtk_adjustment_get_upper - gtk_adjustment_get_page_size + + and requires that upper > lower or the deceleration animation never ends + */ + nVPageSize = std::min(nVPageSize, nVUpper); + + mxScrolledWindow->vadjustment_configure(nVCurrentDocPos, 0, nVUpper, + nVStepIncrement, nVPageIncrement, nVPageSize); +} + +OUString SmEditWindow::GetText() const +{ + OUString aText; + EditEngine *pEditEngine = const_cast< SmEditWindow* >(this)->GetEditEngine(); + OSL_ENSURE( pEditEngine, "EditEngine missing" ); + if (pEditEngine) + aText = pEditEngine->GetText(); + return aText; +} + +void SmEditWindow::SetText(const OUString& rText) +{ + if (!mxTextControl) + return; + mxTextControl->SetText(rText); +} + +void SmEditWindow::Flush() +{ + if (!mxTextControl) + return; + mxTextControl->Flush(); +} + +void SmEditWindow::GrabFocus() +{ + if (!mxTextControl) + return; + mxTextControl->GrabFocus(); +} + +void SmEditTextWindow::SetText(const OUString& rText) +{ + EditEngine *pEditEngine = GetEditEngine(); + OSL_ENSURE( pEditEngine, "EditEngine missing" ); + if (!pEditEngine || pEditEngine->IsModified()) + return; + + EditView* pEditView = GetEditView(); + ESelection eSelection = pEditView->GetSelection(); + + pEditEngine->SetText(rText); + pEditEngine->ClearModifyFlag(); + + // Restarting the timer here, prevents calling the handlers for other (currently inactive) + // math tasks + aModifyIdle.Start(); + + // Apply zoom to smeditwindow text + static_cast<SmEditEngine*>(pEditView->GetEditEngine())->executeZoom(pEditView); + pEditView->SetSelection(eSelection); +} + +void SmEditTextWindow::GetFocus() +{ + WeldEditView::GetFocus(); + + EditEngine *pEditEngine = GetEditEngine(); + if (pEditEngine) + pEditEngine->SetStatusEventHdl(LINK(this, SmEditTextWindow, EditStatusHdl)); +} + +void SmEditTextWindow::LoseFocus() +{ + EditEngine *pEditEngine = GetEditEngine(); + if (pEditEngine) + pEditEngine->SetStatusEventHdl( Link<EditStatus&,void>() ); + + WeldEditView::LoseFocus(); +} + +bool SmEditWindow::IsAllSelected() const +{ + EditEngine *pEditEngine = const_cast<SmEditWindow *>(this)->GetEditEngine(); + if (!pEditEngine) + return false; + EditView* pEditView = GetEditView(); + if (!pEditView) + return false; + bool bRes = false; + ESelection eSelection( pEditView->GetSelection() ); + sal_Int32 nParaCnt = pEditEngine->GetParagraphCount(); + if (!(nParaCnt - 1)) + { + sal_Int32 nTextLen = pEditEngine->GetText().getLength(); + bRes = !eSelection.nStartPos && (eSelection.nEndPos == nTextLen - 1); + } + else + { + bRes = !eSelection.nStartPara && (eSelection.nEndPara == nParaCnt - 1); + } + return bRes; +} + +void SmEditWindow::SelectAll() +{ + if (EditView* pEditView = GetEditView()) + { + // ALL as last two parameters refers to the end of the text + pEditView->SetSelection( ESelection( 0, 0, EE_PARA_ALL, EE_TEXTPOS_ALL ) ); + } +} + +void SmEditWindow::MarkError(const Point &rPos) +{ + if (EditView* pEditView = GetEditView()) + { + const sal_uInt16 nCol = sal::static_int_cast< sal_uInt16 >(rPos.X()); + const sal_uInt16 nRow = sal::static_int_cast< sal_uInt16 >(rPos.Y() - 1); + + pEditView->SetSelection(ESelection(nRow, nCol - 1, nRow, nCol)); + GrabFocus(); + } +} + +void SmEditWindow::SelNextMark() +{ + if (!mxTextControl) + return; + mxTextControl->SelNextMark(); +} + +// Makes selection to next <?> symbol +void SmEditTextWindow::SelNextMark() +{ + EditEngine *pEditEngine = GetEditEngine(); + if (!pEditEngine) + return; + EditView* pEditView = GetEditView(); + if (!pEditView) + return; + + ESelection eSelection = pEditView->GetSelection(); + sal_Int32 nPos = eSelection.nEndPos; + sal_Int32 nCounts = pEditEngine->GetParagraphCount(); + + while (eSelection.nEndPara < nCounts) + { + OUString aText = pEditEngine->GetText(eSelection.nEndPara); + nPos = aText.indexOf("<?>", nPos); + if (nPos != -1) + { + pEditView->SetSelection(ESelection( + eSelection.nEndPara, nPos, eSelection.nEndPara, nPos + 3)); + break; + } + + nPos = 0; + eSelection.nEndPara++; + } +} + +void SmEditWindow::SelPrevMark() +{ + EditEngine *pEditEngine = GetEditEngine(); + if (!pEditEngine) + return; + EditView* pEditView = GetEditView(); + if (!pEditView) + return; + + ESelection eSelection = pEditView->GetSelection(); + sal_Int32 nPara = eSelection.nStartPara; + sal_Int32 nMax = eSelection.nStartPos; + OUString aText(pEditEngine->GetText(nPara)); + static constexpr OUStringLiteral aMark(u"<?>"); + sal_Int32 nPos; + + while ( (nPos = aText.lastIndexOf(aMark, nMax)) < 0 ) + { + if (--nPara < 0) + return; + aText = pEditEngine->GetText(nPara); + nMax = aText.getLength(); + } + pEditView->SetSelection(ESelection(nPara, nPos, nPara, nPos + 3)); +} + +// returns true iff 'rText' contains a mark +static bool HasMark(std::u16string_view rText) +{ + return rText.find(u"<?>") != std::u16string_view::npos; +} + +ESelection SmEditWindow::GetSelection() const +{ + if (mxTextControl) + return mxTextControl->GetSelection(); + return ESelection(); +} + +ESelection SmEditTextWindow::GetSelection() const +{ + // pointer may be 0 when reloading a document and the old view + // was already destroyed + if (EditView* pEditView = GetEditView()) + return pEditView->GetSelection(); + return ESelection(); +} + +void SmEditWindow::SetSelection(const ESelection &rSel) +{ + if (EditView* pEditView = GetEditView()) + pEditView->SetSelection(rSel); + InvalidateSlots(); +} + +bool SmEditWindow::IsEmpty() const +{ + EditEngine *pEditEngine = const_cast<SmEditWindow *>(this)->GetEditEngine(); + bool bEmpty = ( pEditEngine && pEditEngine->GetTextLen() == 0 ); + return bEmpty; +} + +bool SmEditWindow::IsSelected() const +{ + EditView* pEditView = GetEditView(); + return pEditView && pEditView->HasSelection(); +} + +void SmEditTextWindow::UpdateStatus(bool bSetDocModified) +{ + SmModule *pMod = SM_MOD(); + if (pMod && pMod->GetConfig()->IsAutoRedraw()) + Flush(); + + if (SmDocShell *pModifyDoc = bSetDocModified ? mrEditWindow.GetDoc() : nullptr) + pModifyDoc->SetModified(); + + static_cast<SmEditEngine*>(GetEditEngine())->executeZoom(GetEditView()); +} + +void SmEditWindow::UpdateStatus() +{ + mxTextControl->UpdateStatus(/*bSetDocModified*/false); +} + +void SmEditWindow::Cut() +{ + if (mxTextControl) + { + mxTextControl->Cut(); + mxTextControl->UpdateStatus(true); + } +} + +void SmEditWindow::Copy() +{ + if (mxTextControl) + mxTextControl->Copy(); +} + +void SmEditWindow::Paste() +{ + if (mxTextControl) + { + mxTextControl->Paste(); + mxTextControl->UpdateStatus(true); + } +} + +void SmEditWindow::Delete() +{ + if (mxTextControl) + { + mxTextControl->Delete(); + mxTextControl->UpdateStatus(true); + } +} + +void SmEditWindow::InsertText(const OUString& rText) +{ + if (!mxTextControl) + return; + mxTextControl->InsertText(rText); +} + +void SmEditTextWindow::InsertText(const OUString& rText) +{ + EditView* pEditView = GetEditView(); + if (!pEditView) + return; + + // Note: Insertion of a space in front of commands is done here and + // in SmEditWindow::InsertCommand. + ESelection aSelection = pEditView->GetSelection(); + OUString aCurrentFormula = pEditView->GetEditEngine()->GetText(); + sal_Int32 nStartIndex = 0; + + // get the start position (when we get a multi line formula) + for (sal_Int32 nParaPos = 0; nParaPos < aSelection.nStartPara; nParaPos++) + nStartIndex = aCurrentFormula.indexOf("\n", nStartIndex) + 1; + + nStartIndex += aSelection.nStartPos; + + // TODO: unify this function with the InsertCommand: The do the same thing for different + // callers + OUString string(rText); + + OUString selected(pEditView->GetSelected()); + // if we have text selected, use it in the first placeholder + if (!selected.isEmpty()) + string = string.replaceFirst("<?>", selected); + + // put a space before a new command if not in the beginning of a line + if (aSelection.nStartPos > 0 && aCurrentFormula[nStartIndex - 1] != ' ') + string = " " + string; + + pEditView->InsertText(string); + + // Remember start of the selection and move the cursor there afterwards. + aSelection.nEndPara = aSelection.nStartPara; + if (HasMark(string)) + { + aSelection.nEndPos = aSelection.nStartPos; + pEditView->SetSelection(aSelection); + SelNextMark(); + } + else + { // set selection after inserted text + aSelection.nEndPos = aSelection.nStartPos + string.getLength(); + aSelection.nStartPos = aSelection.nEndPos; + pEditView->SetSelection(aSelection); + } + + aModifyIdle.Start(); + StartCursorMove(); + + GrabFocus(); +} + +void SmEditTextWindow::Flush() +{ + EditEngine *pEditEngine = GetEditEngine(); + if (pEditEngine && pEditEngine->IsModified()) + { + pEditEngine->ClearModifyFlag(); + if (SmViewShell *pViewSh = mrEditWindow.GetView()) + { + SfxStringItem aTextToFlush(SID_TEXT, GetText()); + pViewSh->GetViewFrame().GetDispatcher()->ExecuteList( + SID_TEXT, SfxCallMode::RECORD, + { &aTextToFlush }); + } + } + if (aCursorMoveIdle.IsActive()) + { + aCursorMoveIdle.Stop(); + CursorMoveTimerHdl(&aCursorMoveIdle); + } +} + +void SmEditWindow::DeleteEditView() +{ + if (EditView* pEditView = GetEditView()) + { + if (EditEngine* pEditEngine = pEditView->GetEditEngine()) + { + pEditEngine->SetStatusEventHdl( Link<EditStatus&,void>() ); + pEditEngine->RemoveView(pEditView); + } + mxTextControlWin.reset(); + mxTextControl.reset(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/eqnolefilehdr.cxx b/starmath/source/eqnolefilehdr.cxx new file mode 100644 index 0000000000..f211f1aaa9 --- /dev/null +++ b/starmath/source/eqnolefilehdr.cxx @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "eqnolefilehdr.hxx" +#include <sot/storage.hxx> + + +bool GetMathTypeVersion( SotStorage* pStor, sal_uInt8 &nVersion ) +{ + sal_uInt8 nVer = 0; + bool bSuccess = false; + + + // code snippet copied from MathType::Parse + + tools::SvRef<SotStorageStream> xSrc = pStor->OpenSotStream( + "Equation Native", + StreamMode::STD_READ); + if ( (!xSrc.is()) || (ERRCODE_NONE != xSrc->GetError())) + return bSuccess; + SotStorageStream *pS = xSrc.get(); + pS->SetEndian( SvStreamEndian::LITTLE ); + + EQNOLEFILEHDR aHdr; + aHdr.Read(pS); + pS->ReadUChar( nVer ); + + if (!pS->GetError()) + { + nVersion = nVer; + bSuccess = true; + } + return bSuccess; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/eqnolefilehdr.hxx b/starmath/source/eqnolefilehdr.hxx new file mode 100644 index 0000000000..7fd3a476a8 --- /dev/null +++ b/starmath/source/eqnolefilehdr.hxx @@ -0,0 +1,74 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <sal/types.h> +#include <tools/stream.hxx> + +class SotStorage; + +#define EQNOLEFILEHDR_SIZE 28 + +class EQNOLEFILEHDR +{ +public: + EQNOLEFILEHDR() : nCBHdr(0),nVersion(0), + nCf(0),nCBObject(0),nReserved1(0),nReserved2(0), + nReserved3(0), nReserved4(0) {} + explicit EQNOLEFILEHDR(sal_uInt32 nLenMTEF) : nCBHdr(0x1c),nVersion(0x20000), + nCf(0xc1c6),nCBObject(nLenMTEF),nReserved1(0),nReserved2(0x0014F690), + nReserved3(0x0014EBB4), nReserved4(0) {} + + sal_uInt16 nCBHdr; // length of header, sizeof(EQNOLEFILEHDR) = 28 + sal_uInt32 nVersion; // hiword = 2, loword = 0 + sal_uInt16 nCf; // clipboard format ("MathType EF") + sal_uInt32 nCBObject; // length of MTEF data following this header + sal_uInt32 nReserved1; // not used + sal_uInt32 nReserved2; // not used + sal_uInt32 nReserved3; // not used + sal_uInt32 nReserved4; // not used + + void Read(SvStream* pS) + { + pS->ReadUInt16( nCBHdr ); + pS->ReadUInt32( nVersion ); + pS->ReadUInt16( nCf ); + pS->ReadUInt32( nCBObject ); + pS->ReadUInt32( nReserved1 ); + pS->ReadUInt32( nReserved2 ); + pS->ReadUInt32( nReserved3 ); + pS->ReadUInt32( nReserved4 ); + } + void Write(SvStream* pS) + { + pS->WriteUInt16( nCBHdr ); + pS->WriteUInt32( nVersion ); + pS->WriteUInt16( nCf ); + pS->WriteUInt32( nCBObject ); + pS->WriteUInt32( nReserved1 ); + pS->WriteUInt32( nReserved2 ); + pS->WriteUInt32( nReserved3 ); + pS->WriteUInt32( nReserved4 ); + } +}; + +bool GetMathTypeVersion( SotStorage* pStor, sal_uInt8 &nVersion ); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/format.cxx b/starmath/source/format.cxx new file mode 100644 index 0000000000..9440ffb6b1 --- /dev/null +++ b/starmath/source/format.cxx @@ -0,0 +1,155 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <format.hxx> + + +SmFormat::SmFormat() +: aBaseSize(0, o3tl::convert(12, o3tl::Length::pt, SmO3tlLengthUnit())) +{ + eHorAlign = SmHorAlign::Center; + nGreekCharStyle = 0; + bIsTextmode = bIsRightToLeft = bScaleNormalBrackets = false; + + vSize[SIZ_TEXT] = 100; + vSize[SIZ_INDEX] = 60; + vSize[SIZ_FUNCTION] = + vSize[SIZ_OPERATOR] = 100; + vSize[SIZ_LIMITS] = 60; + + vDist[DIS_HORIZONTAL] = 10; + vDist[DIS_VERTICAL] = 5; + vDist[DIS_ROOT] = 0; + vDist[DIS_SUPERSCRIPT] = + vDist[DIS_SUBSCRIPT] = 20; + vDist[DIS_NUMERATOR] = + vDist[DIS_DENOMINATOR] = 0; + vDist[DIS_FRACTION] = 10; + vDist[DIS_STROKEWIDTH] = 5; + vDist[DIS_UPPERLIMIT] = + vDist[DIS_LOWERLIMIT] = 0; + vDist[DIS_BRACKETSIZE] = + vDist[DIS_BRACKETSPACE] = 5; + vDist[DIS_MATRIXROW] = 3; + vDist[DIS_MATRIXCOL] = 30; + vDist[DIS_ORNAMENTSIZE] = + vDist[DIS_ORNAMENTSPACE] = 0; + vDist[DIS_OPERATORSIZE] = 50; + vDist[DIS_OPERATORSPACE] = 20; + vDist[DIS_LEFTSPACE] = + vDist[DIS_RIGHTSPACE] = 0; + vDist[DIS_TOPSPACE] = + vDist[DIS_BOTTOMSPACE] = + vDist[DIS_NORMALBRACKETSIZE] = 0; + + vFont[FNT_VARIABLE] = + vFont[FNT_FUNCTION] = + vFont[FNT_NUMBER] = + vFont[FNT_TEXT] = + vFont[FNT_SERIF] = SmFace(FNTNAME_TIMES, aBaseSize); + vFont[FNT_SANS] = SmFace(FNTNAME_HELV, aBaseSize); + vFont[FNT_FIXED] = SmFace(FNTNAME_COUR, aBaseSize); + vFont[FNT_MATH] = SmFace(FNTNAME_MATH, aBaseSize); + + vFont[FNT_MATH].SetCharSet( RTL_TEXTENCODING_UNICODE ); + + vFont[FNT_VARIABLE].SetItalic(ITALIC_NORMAL); + vFont[FNT_FUNCTION].SetItalic(ITALIC_NONE); + vFont[FNT_NUMBER] .SetItalic(ITALIC_NONE); + vFont[FNT_TEXT] .SetItalic(ITALIC_NONE); + vFont[FNT_SERIF] .SetItalic(ITALIC_NONE); + vFont[FNT_SANS] .SetItalic(ITALIC_NONE); + vFont[FNT_FIXED] .SetItalic(ITALIC_NONE); + + for ( sal_uInt16 i = FNT_BEGIN; i <= FNT_END; i++ ) + { + SmFace &rFace = vFont[i]; + rFace.SetTransparent( true ); + rFace.SetAlignment( ALIGN_BASELINE ); + rFace.SetColor( COL_AUTO ); + bDefaultFont[i] = false; + } +} + + +void SmFormat::SetFont(sal_uInt16 nIdent, const SmFace &rFont, bool bDefault ) +{ + vFont[nIdent] = rFont; + vFont[nIdent].SetTransparent( true ); + vFont[nIdent].SetAlignment( ALIGN_BASELINE ); + + bDefaultFont[nIdent] = bDefault; +} + +SmFormat & SmFormat::operator = (const SmFormat &rFormat) +{ + SetBaseSize(rFormat.GetBaseSize()); + SetHorAlign(rFormat.GetHorAlign()); + SetTextmode(rFormat.IsTextmode()); + SetRightToLeft(rFormat.IsRightToLeft()); + SetGreekCharStyle(rFormat.GetGreekCharStyle()); + SetScaleNormalBrackets(rFormat.IsScaleNormalBrackets()); + + sal_uInt16 i; + for (i = FNT_BEGIN; i <= FNT_END; i++) + { + SetFont(i, rFormat.GetFont(i)); + SetDefaultFont(i, rFormat.IsDefaultFont(i)); + } + for (i = SIZ_BEGIN; i <= SIZ_END; i++) + SetRelSize(i, rFormat.GetRelSize(i)); + for (i = DIS_BEGIN; i <= DIS_END; i++) + SetDistance(i, rFormat.GetDistance(i)); + + return *this; +} + + +bool SmFormat::operator == (const SmFormat &rFormat) const +{ + bool bRes = aBaseSize == rFormat.aBaseSize && + eHorAlign == rFormat.eHorAlign && + nGreekCharStyle == rFormat.nGreekCharStyle && + bIsTextmode == rFormat.bIsTextmode && + bIsRightToLeft == rFormat.bIsRightToLeft && + bScaleNormalBrackets == rFormat.bScaleNormalBrackets; + + sal_uInt16 i; + for (i = 0; i <= SIZ_END && bRes; ++i) + { + if (vSize[i] != rFormat.vSize[i]) + bRes = false; + } + for (i = 0; i <= DIS_END && bRes; ++i) + { + if (vDist[i] != rFormat.vDist[i]) + bRes = false; + } + for (i = 0; i <= FNT_END && bRes; ++i) + { + if (vFont[i] != rFormat.vFont[i] || + bDefaultFont[i] != rFormat.bDefaultFont[i]) + bRes = false; + } + + return bRes; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/mathml/attribute.cxx b/starmath/source/mathml/attribute.cxx new file mode 100644 index 0000000000..ca4a86290d --- /dev/null +++ b/starmath/source/mathml/attribute.cxx @@ -0,0 +1,479 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <mathml/attribute.hxx> + +void SmMlAttribute::clearPreviousAttributeValue() +{ + switch (m_aSmMlAttributeValueType) + { + case SmMlAttributeValueType::NMlEmpty: + break; + case SmMlAttributeValueType::MlHref: + if (m_aAttributeValue.m_aHref.m_aLnk) + delete m_aAttributeValue.m_aHref.m_aLnk; + break; + case SmMlAttributeValueType::MlLspace: + if (m_aAttributeValue.m_aLspace.m_aLengthValue.m_aOriginalText) + delete m_aAttributeValue.m_aLspace.m_aLengthValue.m_aOriginalText; + break; + case SmMlAttributeValueType::MlMathsize: + if (m_aAttributeValue.m_aMathsize.m_aLengthValue.m_aOriginalText) + delete m_aAttributeValue.m_aMathsize.m_aLengthValue.m_aOriginalText; + break; + case SmMlAttributeValueType::MlMaxsize: + if (m_aAttributeValue.m_aMaxsize.m_aLengthValue.m_aOriginalText) + delete m_aAttributeValue.m_aMaxsize.m_aLengthValue.m_aOriginalText; + break; + case SmMlAttributeValueType::MlMinsize: + if (m_aAttributeValue.m_aMinsize.m_aLengthValue.m_aOriginalText) + delete m_aAttributeValue.m_aMinsize.m_aLengthValue.m_aOriginalText; + break; + case SmMlAttributeValueType::MlRspace: + if (m_aAttributeValue.m_aRspace.m_aLengthValue.m_aOriginalText) + delete m_aAttributeValue.m_aRspace.m_aLengthValue.m_aOriginalText; + break; + default: + break; + } +} + +void SmMlAttribute::setDefaultAttributeValue() +{ + switch (m_aSmMlAttributeValueType) + { + case SmMlAttributeValueType::NMlEmpty: + break; + case SmMlAttributeValueType::MlAccent: + m_aAttributeValue.m_aAccent.m_aAccent = SmMlAttributeValueAccent::MlFalse; + break; + case SmMlAttributeValueType::MlDir: + m_aAttributeValue.m_aDir.m_aDir = SmMlAttributeValueDir::MlLtr; + break; + case SmMlAttributeValueType::MlDisplaystyle: + m_aAttributeValue.m_aDisplaystyle.m_aDisplaystyle + = SmMlAttributeValueDisplaystyle::MlFalse; + break; + case SmMlAttributeValueType::MlFence: + m_aAttributeValue.m_aFence.m_aFence = SmMlAttributeValueFence::MlFalse; + break; + case SmMlAttributeValueType::MlForm: + m_aAttributeValue.m_aForm.m_aForm = SmMlAttributeValueForm::MlInfix; + break; + case SmMlAttributeValueType::MlHref: + m_aAttributeValue.m_aHref.m_aHref = SmMlAttributeValueHref::NMlEmpty; + m_aAttributeValue.m_aHref.m_aLnk = new OUString(u""_ustr); + break; + case SmMlAttributeValueType::MlLspace: + m_aAttributeValue.m_aLspace.m_aLengthValue.m_aLengthUnit = SmLengthUnit::MlEm; + m_aAttributeValue.m_aLspace.m_aLengthValue.m_aLengthValue = 5.0 / 18; + m_aAttributeValue.m_aLspace.m_aLengthValue.m_aOriginalText + = new OUString(u"5/18em"_ustr); + break; + case SmMlAttributeValueType::MlMathbackground: + m_aAttributeValue.m_aMathbackground.m_aMathbackground + = SmMlAttributeValueMathbackground::MlTransparent; + break; + case SmMlAttributeValueType::MlMathcolor: + m_aAttributeValue.m_aMathcolor.m_aMathcolor = SmMlAttributeValueMathcolor::MlDefault; + break; + case SmMlAttributeValueType::MlMathsize: + m_aAttributeValue.m_aMathsize.m_aLengthValue.m_aLengthUnit = SmLengthUnit::MlP; + m_aAttributeValue.m_aMathsize.m_aLengthValue.m_aLengthValue = 100; + m_aAttributeValue.m_aMathsize.m_aLengthValue.m_aOriginalText + = new OUString(u"100%"_ustr); + break; + case SmMlAttributeValueType::MlMathvariant: + m_aAttributeValue.m_aMathvariant.m_aMathvariant = SmMlAttributeValueMathvariant::normal; + break; + case SmMlAttributeValueType::MlMaxsize: + m_aAttributeValue.m_aMaxsize.m_aMaxsize = SmMlAttributeValueMaxsize::MlInfinity; + m_aAttributeValue.m_aMaxsize.m_aLengthValue.m_aLengthUnit = SmLengthUnit::MlP; + m_aAttributeValue.m_aMaxsize.m_aLengthValue.m_aLengthValue = 10000; + m_aAttributeValue.m_aMaxsize.m_aLengthValue.m_aOriginalText + = new OUString(u"10000%"_ustr); + break; + case SmMlAttributeValueType::MlMinsize: + m_aAttributeValue.m_aMinsize.m_aLengthValue.m_aLengthUnit = SmLengthUnit::MlP; + m_aAttributeValue.m_aMinsize.m_aLengthValue.m_aLengthValue = 1; + m_aAttributeValue.m_aMinsize.m_aLengthValue.m_aOriginalText = new OUString(u"1%"_ustr); + break; + case SmMlAttributeValueType::MlMovablelimits: + m_aAttributeValue.m_aMovablelimits.m_aMovablelimits + = SmMlAttributeValueMovablelimits::MlFalse; + break; + case SmMlAttributeValueType::MlRspace: + m_aAttributeValue.m_aRspace.m_aLengthValue.m_aLengthUnit = SmLengthUnit::MlEm; + m_aAttributeValue.m_aRspace.m_aLengthValue.m_aLengthValue = 5.0 / 18; + m_aAttributeValue.m_aRspace.m_aLengthValue.m_aOriginalText + = new OUString(u"5/18em"_ustr); + break; + case SmMlAttributeValueType::MlSeparator: + m_aAttributeValue.m_aSeparator.m_aSeparator = SmMlAttributeValueSeparator::MlFalse; + break; + case SmMlAttributeValueType::MlStretchy: + m_aAttributeValue.m_aStretchy.m_aStretchy = SmMlAttributeValueStretchy::MlFalse; + break; + case SmMlAttributeValueType::MlSymmetric: + m_aAttributeValue.m_aSymmetric.m_aSymmetric = SmMlAttributeValueSymmetric::MlFalse; + break; + } +} + +void SmMlAttribute::setAttributeValue(const SmMlAttribute* aAttribute) +{ + switch (aAttribute->getMlAttributeValueType()) + { + case SmMlAttributeValueType::NMlEmpty: + clearPreviousAttributeValue(); + m_aSmMlAttributeValueType = SmMlAttributeValueType::NMlEmpty; + break; + case SmMlAttributeValueType::MlAccent: + setMlAccent(aAttribute->getMlAccent()); + break; + case SmMlAttributeValueType::MlDir: + setMlDir(aAttribute->getMlDir()); + break; + case SmMlAttributeValueType::MlDisplaystyle: + setMlDisplaystyle(aAttribute->getMlDisplaystyle()); + break; + case SmMlAttributeValueType::MlFence: + setMlFence(aAttribute->getMlFence()); + break; + case SmMlAttributeValueType::MlForm: + setMlForm(aAttribute->getMlForm()); + break; + case SmMlAttributeValueType::MlHref: + setMlHref(aAttribute->getMlHref()); + break; + case SmMlAttributeValueType::MlLspace: + setMlLspace(aAttribute->getMlLspace()); + break; + case SmMlAttributeValueType::MlMathbackground: + setMlMathbackground(aAttribute->getMlMathbackground()); + break; + case SmMlAttributeValueType::MlMathcolor: + setMlMathcolor(aAttribute->getMlMathcolor()); + break; + case SmMlAttributeValueType::MlMathsize: + setMlMathsize(aAttribute->getMlMathsize()); + break; + case SmMlAttributeValueType::MlMathvariant: + setMlMathvariant(aAttribute->getMlMathvariant()); + break; + case SmMlAttributeValueType::MlMaxsize: + setMlMaxsize(aAttribute->getMlMaxsize()); + break; + case SmMlAttributeValueType::MlMinsize: + setMlMinsize(aAttribute->getMlMinsize()); + break; + case SmMlAttributeValueType::MlMovablelimits: + setMlMovablelimits(aAttribute->getMlMovablelimits()); + break; + case SmMlAttributeValueType::MlRspace: + setMlRspace(aAttribute->getMlRspace()); + break; + case SmMlAttributeValueType::MlSeparator: + setMlSeparator(aAttribute->getMlSeparator()); + break; + case SmMlAttributeValueType::MlStretchy: + setMlStretchy(aAttribute->getMlStretchy()); + break; + case SmMlAttributeValueType::MlSymmetric: + setMlSymmetric(aAttribute->getMlSymmetric()); + break; + } +} + +/* get values */ +/*************************************************************************************************/ + +const struct SmMlAccent* SmMlAttribute::getMlAccent() const +{ + if (m_aSmMlAttributeValueType == SmMlAttributeValueType::MlAccent) + return &m_aAttributeValue.m_aAccent; + return nullptr; +} + +const struct SmMlDir* SmMlAttribute::getMlDir() const +{ + if (m_aSmMlAttributeValueType == SmMlAttributeValueType::MlDir) + return &m_aAttributeValue.m_aDir; + return nullptr; +} + +const struct SmMlDisplaystyle* SmMlAttribute::getMlDisplaystyle() const +{ + if (m_aSmMlAttributeValueType == SmMlAttributeValueType::MlDisplaystyle) + return &m_aAttributeValue.m_aDisplaystyle; + return nullptr; +} + +const struct SmMlFence* SmMlAttribute::getMlFence() const +{ + if (m_aSmMlAttributeValueType == SmMlAttributeValueType::MlFence) + return &m_aAttributeValue.m_aFence; + return nullptr; +} + +const struct SmMlForm* SmMlAttribute::getMlForm() const +{ + if (m_aSmMlAttributeValueType == SmMlAttributeValueType::MlForm) + return &m_aAttributeValue.m_aForm; + return nullptr; +} + +const struct SmMlHref* SmMlAttribute::getMlHref() const +{ + if (m_aSmMlAttributeValueType == SmMlAttributeValueType::MlHref) + return &m_aAttributeValue.m_aHref; + return nullptr; +} + +const struct SmMlLspace* SmMlAttribute::getMlLspace() const +{ + if (m_aSmMlAttributeValueType == SmMlAttributeValueType::MlLspace) + return &m_aAttributeValue.m_aLspace; + return nullptr; +} + +const struct SmMlMathbackground* SmMlAttribute::getMlMathbackground() const +{ + if (m_aSmMlAttributeValueType == SmMlAttributeValueType::MlMathbackground) + return &m_aAttributeValue.m_aMathbackground; + return nullptr; +} + +const struct SmMlMathcolor* SmMlAttribute::getMlMathcolor() const +{ + if (m_aSmMlAttributeValueType == SmMlAttributeValueType::MlMathcolor) + return &m_aAttributeValue.m_aMathcolor; + return nullptr; +} + +const struct SmMlMathsize* SmMlAttribute::getMlMathsize() const +{ + if (m_aSmMlAttributeValueType == SmMlAttributeValueType::MlAccent) + return &m_aAttributeValue.m_aMathsize; + return nullptr; +} + +const struct SmMlMathvariant* SmMlAttribute::getMlMathvariant() const +{ + if (m_aSmMlAttributeValueType == SmMlAttributeValueType::MlMathvariant) + return &m_aAttributeValue.m_aMathvariant; + return nullptr; +} + +const struct SmMlMaxsize* SmMlAttribute::getMlMaxsize() const +{ + if (m_aSmMlAttributeValueType == SmMlAttributeValueType::MlMaxsize) + return &m_aAttributeValue.m_aMaxsize; + return nullptr; +} + +const struct SmMlMinsize* SmMlAttribute::getMlMinsize() const +{ + if (m_aSmMlAttributeValueType == SmMlAttributeValueType::MlMinsize) + return &m_aAttributeValue.m_aMinsize; + return nullptr; +} + +const struct SmMlMovablelimits* SmMlAttribute::getMlMovablelimits() const +{ + if (m_aSmMlAttributeValueType == SmMlAttributeValueType::MlMovablelimits) + return &m_aAttributeValue.m_aMovablelimits; + return nullptr; +} + +const struct SmMlRspace* SmMlAttribute::getMlRspace() const +{ + if (m_aSmMlAttributeValueType == SmMlAttributeValueType::MlRspace) + return &m_aAttributeValue.m_aRspace; + return nullptr; +} + +const struct SmMlSeparator* SmMlAttribute::getMlSeparator() const +{ + if (m_aSmMlAttributeValueType == SmMlAttributeValueType::MlSeparator) + return &m_aAttributeValue.m_aSeparator; + return nullptr; +} + +const struct SmMlStretchy* SmMlAttribute::getMlStretchy() const +{ + if (m_aSmMlAttributeValueType == SmMlAttributeValueType::MlStretchy) + return &m_aAttributeValue.m_aStretchy; + return nullptr; +} + +const struct SmMlSymmetric* SmMlAttribute::getMlSymmetric() const +{ + if (m_aSmMlAttributeValueType == SmMlAttributeValueType::MlSymmetric) + return &m_aAttributeValue.m_aSymmetric; + return nullptr; +} + +/* set values */ +/*************************************************************************************************/ + +void SmMlAttribute::setMlAccent(const SmMlAccent* aAccent) +{ + m_bSet = true; + clearPreviousAttributeValue(); + m_aAttributeValue.m_aAccent.m_aAccent = aAccent->m_aAccent; +} + +void SmMlAttribute::setMlDir(const SmMlDir* aDir) +{ + m_bSet = true; + clearPreviousAttributeValue(); + m_aAttributeValue.m_aDir.m_aDir = aDir->m_aDir; +} + +void SmMlAttribute::setMlDisplaystyle(const SmMlDisplaystyle* aDisplaystyle) +{ + m_bSet = true; + clearPreviousAttributeValue(); + m_aAttributeValue.m_aDisplaystyle.m_aDisplaystyle = aDisplaystyle->m_aDisplaystyle; +} + +void SmMlAttribute::setMlFence(const SmMlFence* aFence) +{ + m_bSet = true; + clearPreviousAttributeValue(); + m_aAttributeValue.m_aFence.m_aFence = aFence->m_aFence; +} + +void SmMlAttribute::setMlForm(const SmMlForm* aForm) +{ + m_bSet = true; + clearPreviousAttributeValue(); + m_aAttributeValue.m_aForm.m_aForm = aForm->m_aForm; +} + +void SmMlAttribute::setMlHref(const SmMlHref* aHref) +{ + m_bSet = true; + clearPreviousAttributeValue(); + m_aAttributeValue.m_aHref.m_aHref = aHref->m_aHref; + m_aAttributeValue.m_aHref.m_aLnk = new OUString(*aHref->m_aLnk); +} + +void SmMlAttribute::setMlLspace(const SmMlLspace* aLspace) +{ + m_bSet = true; + clearPreviousAttributeValue(); + m_aAttributeValue.m_aLspace.m_aLengthValue.m_aLengthUnit + = aLspace->m_aLengthValue.m_aLengthUnit; + m_aAttributeValue.m_aLspace.m_aLengthValue.m_aLengthValue + = aLspace->m_aLengthValue.m_aLengthValue; + m_aAttributeValue.m_aLspace.m_aLengthValue.m_aOriginalText + = new OUString(*aLspace->m_aLengthValue.m_aOriginalText); +} + +void SmMlAttribute::setMlMathbackground(const SmMlMathbackground* aMathbackground) +{ + m_bSet = true; + clearPreviousAttributeValue(); + m_aAttributeValue.m_aMathbackground.m_aMathbackground = aMathbackground->m_aMathbackground; +} + +void SmMlAttribute::setMlMathcolor(const SmMlMathcolor* aMathcolor) +{ + m_bSet = true; + clearPreviousAttributeValue(); + m_aAttributeValue.m_aMathcolor.m_aMathcolor = aMathcolor->m_aMathcolor; +} + +void SmMlAttribute::setMlMathsize(const SmMlMathsize* aMathsize) +{ + m_bSet = true; + clearPreviousAttributeValue(); + m_aAttributeValue.m_aMathsize.m_aLengthValue.m_aLengthUnit + = aMathsize->m_aLengthValue.m_aLengthUnit; + m_aAttributeValue.m_aMathsize.m_aLengthValue.m_aLengthValue + = aMathsize->m_aLengthValue.m_aLengthValue; + m_aAttributeValue.m_aMathsize.m_aLengthValue.m_aOriginalText + = new OUString(*aMathsize->m_aLengthValue.m_aOriginalText); +} + +void SmMlAttribute::setMlMathvariant(const SmMlMathvariant* aMathvariant) +{ + m_bSet = true; + clearPreviousAttributeValue(); + m_aAttributeValue.m_aMathvariant.m_aMathvariant = aMathvariant->m_aMathvariant; +} + +void SmMlAttribute::setMlMaxsize(const SmMlMaxsize* aMaxsize) +{ + m_bSet = true; + clearPreviousAttributeValue(); + m_aAttributeValue.m_aMaxsize.m_aMaxsize = aMaxsize->m_aMaxsize; + m_aAttributeValue.m_aMaxsize.m_aLengthValue.m_aLengthUnit + = aMaxsize->m_aLengthValue.m_aLengthUnit; + m_aAttributeValue.m_aMaxsize.m_aLengthValue.m_aLengthValue + = aMaxsize->m_aLengthValue.m_aLengthValue; + m_aAttributeValue.m_aMaxsize.m_aLengthValue.m_aOriginalText + = new OUString(*aMaxsize->m_aLengthValue.m_aOriginalText); +} + +void SmMlAttribute::setMlMinsize(const SmMlMinsize* aMinsize) +{ + m_bSet = true; + clearPreviousAttributeValue(); + m_aAttributeValue.m_aMinsize.m_aLengthValue.m_aLengthUnit + = aMinsize->m_aLengthValue.m_aLengthUnit; + m_aAttributeValue.m_aMinsize.m_aLengthValue.m_aLengthValue + = aMinsize->m_aLengthValue.m_aLengthValue; + m_aAttributeValue.m_aMinsize.m_aLengthValue.m_aOriginalText + = new OUString(*aMinsize->m_aLengthValue.m_aOriginalText); +} + +void SmMlAttribute::setMlMovablelimits(const SmMlMovablelimits* aMovablelimits) +{ + m_bSet = true; + clearPreviousAttributeValue(); + m_aAttributeValue.m_aMovablelimits.m_aMovablelimits = aMovablelimits->m_aMovablelimits; +} + +void SmMlAttribute::setMlRspace(const SmMlRspace* aRspace) +{ + m_bSet = true; + clearPreviousAttributeValue(); + m_aAttributeValue.m_aRspace.m_aLengthValue.m_aLengthUnit + = aRspace->m_aLengthValue.m_aLengthUnit; + m_aAttributeValue.m_aRspace.m_aLengthValue.m_aLengthValue + = aRspace->m_aLengthValue.m_aLengthValue; + m_aAttributeValue.m_aRspace.m_aLengthValue.m_aOriginalText + = new OUString(*aRspace->m_aLengthValue.m_aOriginalText); +} + +void SmMlAttribute::setMlSeparator(const SmMlSeparator* aSeparator) +{ + m_bSet = true; + clearPreviousAttributeValue(); + m_aAttributeValue.m_aSeparator.m_aSeparator = aSeparator->m_aSeparator; +} + +void SmMlAttribute::setMlStretchy(const SmMlStretchy* aStretchy) +{ + m_bSet = true; + clearPreviousAttributeValue(); + m_aAttributeValue.m_aStretchy.m_aStretchy = aStretchy->m_aStretchy; +} + +void SmMlAttribute::setMlSymmetric(const SmMlSymmetric* aSymmetric) +{ + m_bSet = true; + clearPreviousAttributeValue(); + m_aAttributeValue.m_aSymmetric.m_aSymmetric = aSymmetric->m_aSymmetric; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/starmath/source/mathml/def.cxx b/starmath/source/mathml/def.cxx new file mode 100644 index 0000000000..484dcd6653 --- /dev/null +++ b/starmath/source/mathml/def.cxx @@ -0,0 +1,124 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <mathml/attribute.hxx> + +SmMlAttributePos starmathdatabase::MlAttributeListEmpty[] = { + // clang-format off + { SmMlAttributeValueType::NMlEmpty, 0 } + // clang-format on +}; + +SmMlAttributePos starmathdatabase::MlAttributeListMath[] = { + // clang-format off + { SmMlAttributeValueType::NMlEmpty, 0 } + // clang-format on +}; + +SmMlAttributePos starmathdatabase::MlAttributeListMi[] = { + // clang-format off + { SmMlAttributeValueType::MlHref, 0 }, + { SmMlAttributeValueType::MlDir, 1 }, + { SmMlAttributeValueType::MlMathbackground, 2 }, + { SmMlAttributeValueType::MlMathcolor, 3 }, + { SmMlAttributeValueType::MlDisplaystyle, 4 }, + { SmMlAttributeValueType::MlMathsize, 5 }, + { SmMlAttributeValueType::MlMathvariant, 6 } + // clang-format on +}; + +SmMlAttributePos starmathdatabase::MlAttributeListMerror[] = { + // clang-format off + { SmMlAttributeValueType::MlHref, 0 }, + { SmMlAttributeValueType::MlMathbackground, 1 }, + { SmMlAttributeValueType::MlMathcolor, 2 }, + { SmMlAttributeValueType::MlDisplaystyle, 3 } + // clang-format on +}; + +SmMlAttributePos starmathdatabase::MlAttributeListMn[] = { + // clang-format off + { SmMlAttributeValueType::MlHref, 0 }, + { SmMlAttributeValueType::MlDir, 1 }, + { SmMlAttributeValueType::MlMathbackground, 2 }, + { SmMlAttributeValueType::MlMathcolor, 3 }, + { SmMlAttributeValueType::MlDisplaystyle, 4 }, + { SmMlAttributeValueType::MlMathsize, 5 }, + { SmMlAttributeValueType::MlMathvariant, 6 } + // clang-format on +}; + +SmMlAttributePos starmathdatabase::MlAttributeListMo[] = { + // clang-format off + { SmMlAttributeValueType::MlHref, 0 }, + { SmMlAttributeValueType::MlDir, 1 }, + { SmMlAttributeValueType::MlMathbackground, 2 }, + { SmMlAttributeValueType::MlMathcolor, 3 }, + { SmMlAttributeValueType::MlDisplaystyle, 4 }, + { SmMlAttributeValueType::MlMathsize, 5 }, + { SmMlAttributeValueType::MlMathvariant, 6 }, + { SmMlAttributeValueType::MlFence, 7 }, + { SmMlAttributeValueType::MlForm, 8 }, + { SmMlAttributeValueType::MlMaxsize, 9 }, + { SmMlAttributeValueType::MlMinsize, 10 }, + { SmMlAttributeValueType::MlMovablelimits, 11 }, + { SmMlAttributeValueType::MlLspace, 12 }, + { SmMlAttributeValueType::MlRspace, 13 }, + { SmMlAttributeValueType::MlAccent, 14 }, + { SmMlAttributeValueType::MlStretchy, 15 }, + { SmMlAttributeValueType::MlSeparator, 16 }, + { SmMlAttributeValueType::MlSymmetric, 17 } + // clang-format on +}; + +SmMlAttributePos starmathdatabase::MlAttributeListMrow[] = { + // clang-format off + { SmMlAttributeValueType::MlHref, 0 }, + { SmMlAttributeValueType::MlDir, 1 }, + { SmMlAttributeValueType::MlMathbackground, 2 }, + { SmMlAttributeValueType::MlMathcolor, 3 } + // clang-format on +}; + +SmMlAttributePos starmathdatabase::MlAttributeListMtext[] = { + // clang-format off + { SmMlAttributeValueType::MlHref, 0 }, + { SmMlAttributeValueType::MlDir, 1 }, + { SmMlAttributeValueType::MlMathbackground, 2 }, + { SmMlAttributeValueType::MlMathcolor, 3 }, + { SmMlAttributeValueType::MlDisplaystyle, 4 }, + { SmMlAttributeValueType::MlMathsize, 5 }, + { SmMlAttributeValueType::MlMathvariant, 6 } + // clang-format on +}; + +SmMlAttributePos starmathdatabase::MlAttributeListMstyle[] = { + // clang-format off + { SmMlAttributeValueType::MlHref, 0 }, + { SmMlAttributeValueType::MlDir, 1 }, + { SmMlAttributeValueType::MlMathbackground, 2 }, + { SmMlAttributeValueType::MlMathcolor, 3 }, + { SmMlAttributeValueType::MlDisplaystyle, 4 }, + { SmMlAttributeValueType::MlMathsize, 5 }, + { SmMlAttributeValueType::MlMathvariant, 6 }, + { SmMlAttributeValueType::MlFence, 7 }, + { SmMlAttributeValueType::MlForm, 8 }, + { SmMlAttributeValueType::MlMaxsize, 9 }, + { SmMlAttributeValueType::MlMinsize, 10 }, + { SmMlAttributeValueType::MlMovablelimits, 11 }, + { SmMlAttributeValueType::MlLspace, 12 }, + { SmMlAttributeValueType::MlRspace, 13 }, + { SmMlAttributeValueType::MlAccent, 14 }, + { SmMlAttributeValueType::MlStretchy, 15 }, + { SmMlAttributeValueType::MlSeparator, 16 }, + { SmMlAttributeValueType::MlSymmetric, 17 } + // clang-format on +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/starmath/source/mathml/element.cxx b/starmath/source/mathml/element.cxx new file mode 100644 index 0000000000..4f8f2a64ff --- /dev/null +++ b/starmath/source/mathml/element.cxx @@ -0,0 +1,136 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <mathml/element.hxx> + +void SmMlElement::SmImplAttributeType() +{ + switch (m_aElementType) + { + case SmMlElementType::NMlEmpty: + m_aAttributePosList = std::vector<SmMlAttributePos>(0); + break; + case SmMlElementType::NMlStructural: + m_aAttributePosList = std::vector<SmMlAttributePos>(0); + break; + case SmMlElementType::NMlSmNode: + m_aAttributePosList = std::vector<SmMlAttributePos>(0); + break; + case SmMlElementType::MlMath: + m_aAttributePosList = std::vector<SmMlAttributePos>(0); + //m_aAttributePosList = std::vector<SmMlAttributePos>(std::begin(starmathdatabase::MlAttributeListMath), std::end(starmathdatabase::MlAttributeListMath)); + break; + case SmMlElementType::MlMi: + m_aAttributePosList + = std::vector<SmMlAttributePos>(std::begin(starmathdatabase::MlAttributeListMi), + std::end(starmathdatabase::MlAttributeListMi)); + break; + case SmMlElementType::MlMerror: + m_aAttributePosList + = std::vector<SmMlAttributePos>(std::begin(starmathdatabase::MlAttributeListMerror), + std::end(starmathdatabase::MlAttributeListMerror)); + break; + case SmMlElementType::MlMn: + m_aAttributePosList + = std::vector<SmMlAttributePos>(std::begin(starmathdatabase::MlAttributeListMn), + std::end(starmathdatabase::MlAttributeListMn)); + break; + case SmMlElementType::MlMo: + m_aAttributePosList + = std::vector<SmMlAttributePos>(std::begin(starmathdatabase::MlAttributeListMo), + std::end(starmathdatabase::MlAttributeListMo)); + break; + case SmMlElementType::MlMrow: + m_aAttributePosList + = std::vector<SmMlAttributePos>(std::begin(starmathdatabase::MlAttributeListMrow), + std::end(starmathdatabase::MlAttributeListMrow)); + break; + case SmMlElementType::MlMtext: + m_aAttributePosList + = std::vector<SmMlAttributePos>(std::begin(starmathdatabase::MlAttributeListMtext), + std::end(starmathdatabase::MlAttributeListMtext)); + break; + case SmMlElementType::MlMstyle: + m_aAttributePosList + = std::vector<SmMlAttributePos>(std::begin(starmathdatabase::MlAttributeListMstyle), + std::end(starmathdatabase::MlAttributeListMstyle)); + break; + default: + break; + } + // Create attribute vector with given pattern + m_aAttributeList = starmathdatabase::makeMlAttributeList(m_aAttributePosList); +} + +SmMlAttribute SmMlElement::getAttribute(SmMlAttributeValueType aAttributeType) const +{ + // Look for the attribute position and return if exists + for (size_t i = 0; i < m_aAttributePosList.size(); ++i) + { + if (m_aAttributePosList[i].m_aAttributeValueType == aAttributeType) + return m_aAttributeList[m_aAttributePosList[i].m_nPos]; + } + return SmMlAttribute(); +} + +bool SmMlElement::isAttributeSet(SmMlAttributeValueType aAttributeType) const +{ + // Look for the attribute position and return if exists + for (size_t i = 0; i < m_aAttributePosList.size(); ++i) + { + if (m_aAttributePosList[i].m_aAttributeValueType == aAttributeType) + return m_aAttributeList[m_aAttributePosList[i].m_nPos].isSet(); + } + return false; +} + +void SmMlElement::setAttribute(const SmMlAttribute* aAttribute) +{ + // Look for the attribute position and assign if exists + for (size_t i = 0; i < m_aAttributePosList.size(); ++i) + { + if (m_aAttributePosList[i].m_aAttributeValueType == aAttribute->getMlAttributeValueType()) + { + m_aAttributeList[m_aAttributePosList[i].m_nPos].setMlAttributeValue(aAttribute); + break; + } + } +} + +void SmMlElement::setSubElement(size_t nPos, SmMlElement* aElement) +{ + // This is the new parent element + aElement->setParentElement(this); + aElement->setSubElementId(nPos); + // Check if the vector is long enough + // Careful nOldSize can be 0 and -1 will underflow + // We must put something on the empty locations + size_t nOldSize = m_aSubElements.size(); + if (nPos + 1 > nOldSize) + { + m_aSubElements.resize(nPos + 1); + for (; nOldSize < nPos; ++nOldSize) + m_aSubElements[nOldSize] = nullptr; + } + // Assign value + m_aSubElements[nPos] = aElement; +} + +std::vector<SmMlAttribute> +starmathdatabase::makeMlAttributeList(std::vector<SmMlAttributePos> aAttributePosList) +{ + std::vector<SmMlAttribute> aAttributeList(aAttributePosList.size()); + for (size_t i = 0; i < aAttributePosList.size(); ++i) + { + aAttributeList[i].setMlAttributeValueType(aAttributePosList[i].m_aAttributeValueType); + } + return aAttributeList; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/starmath/source/mathml/export.cxx b/starmath/source/mathml/export.cxx new file mode 100644 index 0000000000..923668f454 --- /dev/null +++ b/starmath/source/mathml/export.cxx @@ -0,0 +1,1090 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +// Our mathml +#include <mathml/export.hxx> +#include <mathml/iterator.hxx> + +// LO tools to use +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/embed/ElementModes.hpp> +#include <com/sun/star/task/XStatusIndicator.hpp> +#include <com/sun/star/uno/Any.h> +#include <com/sun/star/util/MeasureUnit.hpp> +#include <com/sun/star/xml/sax/Writer.hpp> + +// Extra LO tools +#include <comphelper/genericpropertyset.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/propertysetinfo.hxx> +#include <sfx2/frame.hxx> +#include <sfx2/docfile.hxx> +#include <sfx2/sfxsids.hrc> +#include <svl/itemset.hxx> +#include <svl/stritem.hxx> +#include <unotools/streamwrap.hxx> +#include <xmloff/namespacemap.hxx> + +// Our starmath tools +#include <document.hxx> +#include <smmod.hxx> +#include <strings.hrc> +#include <unomodel.hxx> +#include <xparsmlbase.hxx> +#include <starmathdatabase.hxx> + +// Old parser +#include <mathmlexport.hxx> + +using namespace ::com::sun::star; +using namespace xmloff::token; + +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::document; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star; +using namespace ::xmloff::token; + +// SmMLExportWrapper +/*************************************************************************************************/ + +bool SmMLExportWrapper::Export(SfxMedium& rMedium) +{ + bool bRet = true; + uno::Reference<uno::XComponentContext> xContext(comphelper::getProcessComponentContext()); + + // Check all fine + SAL_WARN_IF(m_xModel == nullptr, "starmath", "Missing model"); + SAL_WARN_IF(xContext == nullptr, "starmath", "Missing context"); + if (m_xModel == nullptr || xContext == nullptr) + return false; + + // Get doc shell + SmDocShell* pDocShell = static_cast<SmDocShell*>(m_xModel->GetObjectShell()); + if (pDocShell == nullptr) + { + SAL_WARN("starmath", "Failed to fetch sm document"); + return false; + } + + // Check if it is a standalone window or embed object + bool bEmbedded = SfxObjectCreateMode::EMBEDDED == pDocShell->GetCreateMode(); + + // Medium item set + SfxItemSet& rMediumItemSet = rMedium.GetItemSet(); + if (pDocShell == nullptr) + { + SAL_WARN("starmath", "Failed to get medium item set"); + return false; + } + + // Progress bar ~ + uno::Reference<task::XStatusIndicator> xStatusIndicator; + + if (!bEmbedded) + { + // Extra check to ensure everything is fine + if (pDocShell->GetMedium() != &rMedium) + { + SAL_WARN("starmath", "Input medium and sm document medium do not match"); + //return false; + } + + // Fetch progress bar + const SfxUnoAnyItem* pItem = rMediumItemSet.GetItem(SID_PROGRESS_STATUSBAR_CONTROL); + if (pItem) + { + // set progress range and start status indicator + pItem->GetValue() >>= xStatusIndicator; + xStatusIndicator->start(SmResId(STR_STATSTR_WRITING), 3); + xStatusIndicator->setValue(0); + } + } + + // create XPropertySet with three properties for status indicator + static const comphelper::PropertyMapEntry aInfoMap[]{ + { OUString("UsePrettyPrinting"), 0, cppu::UnoType<bool>::get(), + beans::PropertyAttribute::MAYBEVOID, 0 }, + { OUString("BaseURI"), 0, ::cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::MAYBEVOID, 0 }, + { OUString("StreamRelPath"), 0, ::cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::MAYBEVOID, 0 }, + { OUString("StreamName"), 0, ::cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::MAYBEVOID, 0 } + }; + uno::Reference<beans::XPropertySet> xInfoSet( + comphelper::GenericPropertySet_CreateInstance(new comphelper::PropertySetInfo(aInfoMap))); + + // Always print pretty + xInfoSet->setPropertyValue("UsePrettyPrinting", Any(true)); + + // Set base URI + xInfoSet->setPropertyValue(u"BaseURI"_ustr, Any(rMedium.GetBaseURL(true))); + + if (!m_bFlat) //Storage (Package) of Stream + { + // Fetch the output storage + uno::Reference<embed::XStorage> xStg = rMedium.GetOutputStorage(); + if (xStg == nullptr) + { + SAL_WARN("starmath", "Failed to fetch output storage"); + return false; + } + + // TODO/LATER: handle the case of embedded links gracefully + if (bEmbedded) //&& !pStg->IsRoot() ) + { + const SfxStringItem* pDocHierarchItem + = rMediumItemSet.GetItem(SID_DOC_HIERARCHICALNAME); + if (pDocHierarchItem != nullptr) + { + OUString aName = pDocHierarchItem->GetValue(); + if (!aName.isEmpty()) + xInfoSet->setPropertyValue("StreamRelPath", Any(aName)); + } + } + else + { + // Write file metadata ( data, LO version ... ) + // Note: export through an XML exporter component (storage version) + if (xStatusIndicator.is()) + xStatusIndicator->setValue(1); + + bRet = WriteThroughComponentS(xStg, m_xModel, u"meta.xml", xContext, xInfoSet, + u"com.sun.star.comp.Math.MLOasisMetaExporter", 6); + } + + // Write starmath formula + // Note: export through an XML exporter component (storage version) + if (bRet) + { + if (xStatusIndicator.is()) + xStatusIndicator->setValue(2); + + if (pDocShell->GetSmSyntaxVersion() == 5) + bRet = WriteThroughComponentS(xStg, m_xModel, u"content.xml", xContext, xInfoSet, + u"com.sun.star.comp.Math.XMLContentExporter", 5); + else + bRet = WriteThroughComponentS(xStg, m_xModel, u"content.xml", xContext, xInfoSet, + u"com.sun.star.comp.Math.MLContentExporter", 6); + } + + // Write starmath settings + // Note: export through an XML exporter component (storage version) + if (bRet) + { + if (xStatusIndicator.is()) + xStatusIndicator->setValue(3); + + bRet = WriteThroughComponentS(xStg, m_xModel, u"settings.xml", xContext, xInfoSet, + u"com.sun.star.comp.Math.MLOasisSettingsExporter", 6); + } + } + else + { + // Fetch the output stream + SvStream* pStream = rMedium.GetOutStream(); + if (pStream == nullptr) + { + SAL_WARN("starmath", "Missing output stream"); + return false; + } + uno::Reference<io::XOutputStream> xOut(new utl::OOutputStreamWrapper(*pStream)); + + if (xStatusIndicator.is()) + xStatusIndicator->setValue(1); + + // Write everything in the same place + // Note: export through an XML exporter component (output stream version) + if (pDocShell->GetSmSyntaxVersion() == 5) + bRet = WriteThroughComponentOS(xOut, m_xModel, xContext, xInfoSet, + u"com.sun.star.comp.Math.XMLContentExporter", 5); + else + bRet = WriteThroughComponentOS(xOut, m_xModel, xContext, xInfoSet, + u"com.sun.star.comp.Math.MLContentExporter", 6); + } + + if (xStatusIndicator.is()) + xStatusIndicator->end(); + return bRet; +} + +OUString SmMLExportWrapper::Export(SmMlElement* pElementTree) +{ + uno::Reference<uno::XComponentContext> xContext(comphelper::getProcessComponentContext()); + + // Check all fine + m_pElementTree = nullptr; + SAL_WARN_IF(m_xModel == nullptr, "starmath", "Missing model"); + SAL_WARN_IF(xContext == nullptr, "starmath", "Missing context"); + if (m_xModel == nullptr || xContext == nullptr) + return u""_ustr; + + //Get model + uno::Reference<lang::XComponent> xModelComp = m_xModel; + SAL_WARN_IF(xModelComp == nullptr, "starmath", "Missing model component"); + SmModel* pModel = m_xModel.get(); + SAL_WARN_IF(pModel == nullptr, "starmath", "Failed to get threw uno tunnel"); + if (xModelComp == nullptr || pModel == nullptr) + return u""_ustr; + + // Get doc shell + SmDocShell* pDocShell = static_cast<SmDocShell*>(pModel->GetObjectShell()); + if (pDocShell == nullptr) + { + SAL_WARN("starmath", "Failed to fetch sm document"); + return u""_ustr; + } + + // create XPropertySet with three properties for status indicator + static const comphelper::PropertyMapEntry aInfoMap[]{ + { OUString("UsePrettyPrinting"), 0, cppu::UnoType<bool>::get(), + beans::PropertyAttribute::MAYBEVOID, 0 }, + { OUString("BaseURI"), 0, ::cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::MAYBEVOID, 0 }, + { OUString("StreamRelPath"), 0, ::cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::MAYBEVOID, 0 }, + { OUString("StreamName"), 0, ::cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::MAYBEVOID, 0 } + }; + uno::Reference<beans::XPropertySet> xInfoSet( + comphelper::GenericPropertySet_CreateInstance(new comphelper::PropertySetInfo(aInfoMap))); + + // Always print pretty + xInfoSet->setPropertyValue("UsePrettyPrinting", Any(true)); + + // Fetch mathml tree + m_pElementTree = pElementTree; + + // Write stuff + // Note: export through an XML exporter component (memory stream version) + return WriteThroughComponentMS(xModelComp, xContext, xInfoSet); +} + +// export through an XML exporter component (output stream version) +bool SmMLExportWrapper::WriteThroughComponentOS(const Reference<io::XOutputStream>& xOutputStream, + const Reference<XComponent>& xComponent, + Reference<uno::XComponentContext> const& rxContext, + Reference<beans::XPropertySet> const& rPropSet, + const char16_t* pComponentName, + int_fast16_t nSyntaxVersion) +{ + // We need a output stream but it is already checked by caller + // We need a component but it is already checked by caller + // We need a context but it is already checked by caller + // We need a property set but it is already checked by caller + // We need a component name but it is already checked by caller + + // get sax writer + Reference<xml::sax::XWriter> xSaxWriter = xml::sax::Writer::create(rxContext); + + // connect XML writer to output stream + xSaxWriter->setOutputStream(xOutputStream); + if (m_bUseHTMLMLEntities) + xSaxWriter->setCustomEntityNames(starmathdatabase::icustomMathmlHtmlEntitiesExport); + + // prepare arguments (prepend doc handler to given arguments) + Sequence<Any> aArgs{ Any(xSaxWriter), Any(rPropSet) }; + + // get filter component + auto xExporterData = rxContext->getServiceManager()->createInstanceWithArgumentsAndContext( + OUString(pComponentName), aArgs, rxContext); + Reference<document::XExporter> xExporter(xExporterData, UNO_QUERY); + + // Check everything is fine + if (!xExporter.is()) + { + SAL_WARN("starmath", "can't instantiate export filter component"); + return false; + } + + // connect model and filter + xExporter->setSourceDocument(xComponent); + Reference<XFilter> xFilter(xExporter, UNO_QUERY); + uno::Sequence<PropertyValue> aProps(0); + + // filter + if (nSyntaxVersion == 5) + { + SmXMLExport* pFilter = dynamic_cast<SmXMLExport*>(xFilter.get()); + if (pFilter == nullptr) + { + SAL_WARN("starmath", "Failed to fetch SmMLExport"); + return false; + } + xFilter->filter(aProps); + return pFilter->GetSuccess(); + } + + // filter + SmMLExport* pFilter = dynamic_cast<SmMLExport*>(xFilter.get()); + + // Setup filter + if (pFilter == nullptr) + { + SAL_WARN("starmath", "Failed to fetch SmMLExport"); + return false; + } + pFilter->setUseExportTag(m_bUseExportTag); + pFilter->setElementTree(m_pElementTree); + + // Execute operation + xFilter->filter(aProps); + return pFilter->getSuccess(); +} + +// export through an XML exporter component (storage version) +bool SmMLExportWrapper::WriteThroughComponentS(const Reference<embed::XStorage>& xStorage, + const Reference<XComponent>& xComponent, + const char16_t* pStreamName, + Reference<uno::XComponentContext> const& rxContext, + Reference<beans::XPropertySet> const& rPropSet, + const char16_t* pComponentName, + int_fast16_t nSyntaxVersion) +{ + // We need a storage name but it is already checked by caller + // We need a component name but it is already checked by caller + // We need a stream name but it is already checked by caller + // We need a context but it is already checked by caller + // We need a property set but it is already checked by caller + // We need a component but it is already checked by caller + + // open stream + Reference<io::XStream> xStream; + try + { + xStream = xStorage->openStreamElement( + OUString(pStreamName), embed::ElementModes::READWRITE | embed::ElementModes::TRUNCATE); + } + catch (const uno::Exception&) + { + SAL_WARN("starmath", "Can't create output stream in package"); + return false; + } + + // Set stream as text / xml + uno::Reference<beans::XPropertySet> xSet(xStream, uno::UNO_QUERY); + xSet->setPropertyValue("MediaType", Any(u"text/xml"_ustr)); + + // all streams must be encrypted in encrypted document + xSet->setPropertyValue("UseCommonStoragePasswordEncryption", Any(true)); + + // set Base URL + rPropSet->setPropertyValue("StreamName", Any(OUString(pStreamName))); + + // write the stuff + // Note: export through an XML exporter component (output stream version) + return WriteThroughComponentOS(xStream->getOutputStream(), xComponent, rxContext, rPropSet, + pComponentName, nSyntaxVersion); +} + +// export through an XML exporter component (memory stream version) +OUString +SmMLExportWrapper::WriteThroughComponentMS(const Reference<XComponent>& xComponent, + Reference<uno::XComponentContext> const& rxContext, + Reference<beans::XPropertySet> const& rPropSet) +{ + // We need a component but it is already checked by caller + // We need a context but it is already checked by caller + // We need a property set it is already checked by caller + + // open stream + SvMemoryStream aMemoryStream(8192, 1024); + uno::Reference<io::XOutputStream> xStream(new utl::OOutputStreamWrapper(aMemoryStream)); + + // Set the stream as text + uno::Reference<beans::XPropertySet> xSet(xStream, uno::UNO_QUERY); + xSet->setPropertyValue("MediaType", Any(OUString("text/xml"))); + + // write the stuff + // Note: export through an XML exporter component (output stream version) + bool bOk = WriteThroughComponentOS(xStream, xComponent, rxContext, rPropSet, + u"com.sun.star.comp.Mathml.MLContentExporter", 6); + + // We don't want to read uninitialized data + if (!bOk) + return u""_ustr; + + // Recover data and generate string + OString aString(static_cast<const char*>(aMemoryStream.GetData()), + aMemoryStream.GetSize() / sizeof(char)); + return OStringToOUString(aString, RTL_TEXTENCODING_UTF8); +} + +// SmMLExport technical +/*************************************************************************************************/ + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +Math_MLExporter_get_implementation(css::uno::XComponentContext* context, + css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new SmMLExport(context, "com.sun.star.comp.Math.XMLExporter", + SvXMLExportFlags::OASIS | SvXMLExportFlags::ALL)); +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +Math_MLOasisMetaExporter_get_implementation(css::uno::XComponentContext* context, + css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new SmMLExport(context, "com.sun.star.comp.Math.XMLOasisMetaExporter", + SvXMLExportFlags::OASIS | SvXMLExportFlags::META)); +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +Math_MLOasisSettingsExporter_get_implementation(css::uno::XComponentContext* context, + css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new SmMLExport(context, "com.sun.star.comp.Math.XMLOasisSettingsExporter", + SvXMLExportFlags::OASIS | SvXMLExportFlags::SETTINGS)); +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +Math_MLContentExporter_get_implementation(css::uno::XComponentContext* context, + css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new SmMLExport(context, "com.sun.star.comp.Math.XMLContentExporter", + SvXMLExportFlags::OASIS | SvXMLExportFlags::CONTENT)); +} + +SmDocShell* SmMLExport::getSmDocShell() +{ + SmModel* pModel = comphelper::getFromUnoTunnel<SmModel>(GetModel()); + if (pModel != nullptr) + return static_cast<SmDocShell*>(pModel->GetObjectShell()); + return nullptr; +} + +ErrCode SmMLExport::exportDoc(enum XMLTokenEnum eClass) +{ + if (!(getExportFlags() & SvXMLExportFlags::CONTENT)) + { + // Everything that isn't the formula itself get's default export + SvXMLExport::exportDoc(eClass); + return ERRCODE_NONE; + } + + // Checks if it has to export a particular tree + if (m_pElementTree == nullptr) + { + // Set element tree + SmDocShell* pDocShell = getSmDocShell(); + if (pDocShell != nullptr) + m_pElementTree = pDocShell->GetMlElementTree(); + else + { + m_bSuccess = false; + return SVSTREAM_INVALID_PARAMETER; + } + } + + // Start document and encrypt if necessary + GetDocHandler()->startDocument(); + addChaffWhenEncryptedStorage(); + + // make use of a default namespace + // Math doesn't need namespaces from xmloff, since it now uses default namespaces + // Because that is common with current MathML usage in the web -> ResetNamespaceMap(); + GetNamespaceMap_().Add(u""_ustr, GetXMLToken(XML_N_MATH), XML_NAMESPACE_MATH); + + // Add xmlns line + if (m_bUseExportTag) + { + GetAttrList().AddAttribute(GetNamespaceMap().GetAttrNameByKey(XML_NAMESPACE_MATH), + GetNamespaceMap().GetNameByKey(XML_NAMESPACE_MATH)); + } + + // Export and close document + ExportContent_(); + GetDocHandler()->endDocument(); + + return ERRCODE_NONE; +} + +void SmMLExport::GetViewSettings(Sequence<PropertyValue>& aProps) +{ + // Get the document shell + SmDocShell* pDocShell = getSmDocShell(); + if (pDocShell == nullptr) + { + SAL_WARN("starmath", "Missing document shell so no view settings"); + return; + } + + // Allocate enough memory + aProps.realloc(4); + PropertyValue* pValue = aProps.getArray(); + + // The view settings are the formula display settings + tools::Rectangle aRect(pDocShell->GetVisArea()); + + pValue[0].Name = "ViewAreaTop"; + pValue[0].Value <<= aRect.Top(); + + pValue[1].Name = "ViewAreaLeft"; + pValue[1].Value <<= aRect.Left(); + + pValue[2].Name = "ViewAreaWidth"; + pValue[2].Value <<= aRect.GetWidth(); + + pValue[3].Name = "ViewAreaHeight"; + pValue[3].Value <<= aRect.GetHeight(); +} + +void SmMLExport::GetConfigurationSettings(Sequence<PropertyValue>& rProps) +{ + // Get model property set (settings) + Reference<XPropertySet> xProps(GetModel(), UNO_QUERY); + if (!xProps.is()) + { + SAL_WARN("starmath", "Missing model properties so no configuration settings"); + return; + } + + // Get model property set info (settings values) + Reference<XPropertySetInfo> xPropertySetInfo = xProps->getPropertySetInfo(); + if (!xPropertySetInfo.is()) + { + SAL_WARN("starmath", "Missing model properties info so no configuration settings"); + return; + } + + // Allocate to store the properties + Sequence<Property> aProps = xPropertySetInfo->getProperties(); + const sal_Int32 nCount = aProps.getLength(); + rProps.realloc(nCount); + auto pProps = rProps.getArray(); + + // Copy properties + // This needs further revision + // Based in code mathmlexport.cxx::GetConfigurationSettings + for (sal_Int32 i = 0; i < nCount; ++i) + { + if (aProps[i].Name != "Formula" && aProps[i].Name != "BasicLibraries" + && aProps[i].Name != "DialogLibraries" && aProps[i].Name != "RuntimeUID") + { + pProps[i].Name = aProps[i].Name; + pProps[i].Value = xProps->getPropertyValue(aProps[i].Name); + } + } +} + +SmMLExport::SmMLExport(const css::uno::Reference<css::uno::XComponentContext>& rContext, + OUString const& implementationName, SvXMLExportFlags nExportFlags) + : SvXMLExport(rContext, implementationName, util::MeasureUnit::INCH, XML_MATH, nExportFlags) + , m_pElementTree(nullptr) + , m_bSuccess(true) + , m_bUseExportTag(true) +{ +} + +// SmMLExport +/*************************************************************************************************/ + +void SmMLExport::declareMlError() +{ + SAL_WARN("starmath", "Invalid use of mathml."); + m_bSuccess = false; +} + +void SmMLExport::exportMlAttributeLength(xmloff::token::XMLTokenEnum pAttribute, + const SmLengthValue& aLengthValue) +{ + if (!aLengthValue.m_aOriginalText->isEmpty()) + { + addAttribute(pAttribute, *aLengthValue.m_aOriginalText); + } + else + { + OUStringBuffer aSizeBuffer(64); + aSizeBuffer.append(aLengthValue.m_aLengthValue); + switch (aLengthValue.m_aLengthUnit) + { + case SmLengthUnit::MlEm: + aSizeBuffer.append(u"em"); + break; + case SmLengthUnit::MlEx: + aSizeBuffer.append(u"ex"); + break; + case SmLengthUnit::MlPx: + aSizeBuffer.append(u"px"); + break; + case SmLengthUnit::MlIn: + aSizeBuffer.append(u"in"); + break; + case SmLengthUnit::MlCm: + aSizeBuffer.append(u"cm"); + break; + case SmLengthUnit::MlMm: + aSizeBuffer.append(u"mm"); + break; + case SmLengthUnit::MlPt: + aSizeBuffer.append(u"pt"); + break; + case SmLengthUnit::MlPc: + aSizeBuffer.append(u"pc"); + break; + case SmLengthUnit::MlP: + aSizeBuffer.append(u"%"); + break; + case SmLengthUnit::MlM: + break; + default: + declareMlError(); + break; + } + addAttribute(pAttribute, aSizeBuffer.makeStringAndClear()); + } +} + +void SmMLExport::exportMlAttributes(const SmMlElement* pMlElement) +{ + size_t nAttributeCount = pMlElement->getAttributeCount(); + for (size_t i = 0; i < nAttributeCount; ++i) + { + SmMlAttribute aAttribute = pMlElement->getAttribute(i); + if (!aAttribute.isSet()) + continue; + + switch (aAttribute.getMlAttributeValueType()) + { + case SmMlAttributeValueType::MlAccent: + { + auto aAttributeValue = aAttribute.getMlAccent(); + switch (aAttributeValue->m_aAccent) + { + case SmMlAttributeValueAccent::MlFalse: + addAttribute(XML_ACCENT, XML_FALSE); + break; + case SmMlAttributeValueAccent::MlTrue: + addAttribute(XML_ACCENT, XML_TRUE); + break; + default: + declareMlError(); + break; + } + break; + } + case SmMlAttributeValueType::MlDir: + { + auto aAttributeValue = aAttribute.getMlDir(); + switch (aAttributeValue->m_aDir) + { + case SmMlAttributeValueDir::MlLtr: + addAttribute(XML_DIR, XML_LTR); + break; + case SmMlAttributeValueDir::MlRtl: + addAttribute(XML_DIR, XML_RTL); + break; + default: + declareMlError(); + break; + } + break; + } + case SmMlAttributeValueType::MlDisplaystyle: + { + auto aAttributeValue = aAttribute.getMlDisplaystyle(); + switch (aAttributeValue->m_aDisplaystyle) + { + case SmMlAttributeValueDisplaystyle::MlTrue: + addAttribute(XML_DISPLAYSTYLE, XML_FALSE); + break; + case SmMlAttributeValueDisplaystyle::MlFalse: + addAttribute(XML_DISPLAYSTYLE, XML_TRUE); + break; + default: + declareMlError(); + break; + } + break; + } + case SmMlAttributeValueType::MlFence: + { + auto aAttributeValue = aAttribute.getMlFence(); + switch (aAttributeValue->m_aFence) + { + case SmMlAttributeValueFence::MlTrue: + addAttribute(XML_FENCE, XML_FALSE); + break; + case SmMlAttributeValueFence::MlFalse: + addAttribute(XML_FENCE, XML_TRUE); + break; + default: + declareMlError(); + break; + } + break; + } + case SmMlAttributeValueType::MlHref: + { + auto aAttributeValue = aAttribute.getMlHref(); + switch (aAttributeValue->m_aHref) + { + case SmMlAttributeValueHref::NMlEmpty: + break; + case SmMlAttributeValueHref::NMlValid: + addAttribute(XML_HREF, *aAttributeValue->m_aLnk); + break; + default: + declareMlError(); + break; + } + break; + } + case SmMlAttributeValueType::MlLspace: + { + auto aSizeData = aAttribute.getMlLspace(); + auto aLengthData = aSizeData->m_aLengthValue; + exportMlAttributeLength(XML_LSPACE, aLengthData); + break; + } + case SmMlAttributeValueType::MlMathbackground: + { + auto aAttributeValue = aAttribute.getMlMathbackground(); + switch (aAttributeValue->m_aMathbackground) + { + case SmMlAttributeValueMathbackground::MlTransparent: + addAttribute(XML_MATHBACKGROUND, "transparent"); + break; + case SmMlAttributeValueMathbackground::MlRgb: + { + const OUString& rTextColor = starmathdatabase::Identify_Color_MATHML( + sal_uInt32(aAttributeValue->m_aCol)) + .aIdent; + addAttribute(XML_MATHBACKGROUND, rTextColor); + break; + } + default: + declareMlError(); + break; + } + break; + } + case SmMlAttributeValueType::MlMathcolor: + { + auto aAttributeValue = aAttribute.getMlMathcolor(); + switch (aAttributeValue->m_aMathcolor) + { + case SmMlAttributeValueMathcolor::MlDefault: + break; + case SmMlAttributeValueMathcolor::MlRgb: + { + const OUString& rTextColor = starmathdatabase::Identify_Color_MATHML( + sal_uInt32(aAttributeValue->m_aCol)) + .aIdent; + addAttribute(XML_MATHCOLOR, rTextColor); + break; + } + default: + declareMlError(); + break; + } + break; + } + case SmMlAttributeValueType::MlMathsize: + { + auto aSizeData = aAttribute.getMlMathsize(); + auto aLengthData = aSizeData->m_aLengthValue; + exportMlAttributeLength(XML_MATHSIZE, aLengthData); + break; + } + case SmMlAttributeValueType::MlMathvariant: + { + auto aAttributeValue = aAttribute.getMlMathvariant(); + switch (aAttributeValue->m_aMathvariant) + { + case SmMlAttributeValueMathvariant::normal: + addAttribute(XML_MATHVARIANT, "normal"); + break; + case SmMlAttributeValueMathvariant::bold: + addAttribute(XML_MATHVARIANT, "bold"); + break; + case SmMlAttributeValueMathvariant::italic: + addAttribute(XML_MATHVARIANT, "italic"); + break; + case SmMlAttributeValueMathvariant::double_struck: + addAttribute(XML_MATHVARIANT, "double-struck"); + break; + case SmMlAttributeValueMathvariant::script: + addAttribute(XML_MATHVARIANT, "script"); + break; + case SmMlAttributeValueMathvariant::fraktur: + addAttribute(XML_MATHVARIANT, "fraktur"); + break; + case SmMlAttributeValueMathvariant::sans_serif: + addAttribute(XML_MATHVARIANT, "sans-serif"); + break; + case SmMlAttributeValueMathvariant::monospace: + addAttribute(XML_MATHVARIANT, "monospace"); + break; + case SmMlAttributeValueMathvariant::bold_italic: + addAttribute(XML_MATHVARIANT, "bold-italic"); + break; + case SmMlAttributeValueMathvariant::bold_fraktur: + addAttribute(XML_MATHVARIANT, "bold-fracktur"); + break; + case SmMlAttributeValueMathvariant::bold_script: + addAttribute(XML_MATHVARIANT, "bold-script"); + break; + case SmMlAttributeValueMathvariant::bold_sans_serif: + addAttribute(XML_MATHVARIANT, "bold-sans-serif"); + break; + case SmMlAttributeValueMathvariant::sans_serif_italic: + addAttribute(XML_MATHVARIANT, "sans-serif-italic"); + break; + case SmMlAttributeValueMathvariant::sans_serif_bold_italic: + addAttribute(XML_MATHVARIANT, "sans-serif-bold-italic"); + break; + case SmMlAttributeValueMathvariant::initial: + addAttribute(XML_MATHVARIANT, "initial"); + break; + case SmMlAttributeValueMathvariant::tailed: + addAttribute(XML_MATHVARIANT, "tailed"); + break; + case SmMlAttributeValueMathvariant::looped: + addAttribute(XML_MATHVARIANT, "looped"); + break; + case SmMlAttributeValueMathvariant::stretched: + addAttribute(XML_MATHVARIANT, "stretched"); + break; + default: + declareMlError(); + break; + } + break; + } + case SmMlAttributeValueType::MlMaxsize: + { + auto aSizeData = aAttribute.getMlMaxsize(); + auto aLengthData = aSizeData->m_aLengthValue; + switch (aSizeData->m_aMaxsize) + { + case SmMlAttributeValueMaxsize::MlInfinity: + { + addAttribute(XML_MAXSIZE, XML_INFINITY); + break; + } + case SmMlAttributeValueMaxsize::MlFinite: + { + exportMlAttributeLength(XML_MAXSIZE, aLengthData); + break; + } + } + break; + } + case SmMlAttributeValueType::MlMinsize: + { + auto aSizeData = aAttribute.getMlMinsize(); + auto aLengthData = aSizeData->m_aLengthValue; + exportMlAttributeLength(XML_MINSIZE, aLengthData); + break; + } + case SmMlAttributeValueType::MlMovablelimits: + { + auto aAttributeValue = aAttribute.getMlMovablelimits(); + switch (aAttributeValue->m_aMovablelimits) + { + case SmMlAttributeValueMovablelimits::MlFalse: + addAttribute(XML_MOVABLELIMITS, XML_FALSE); + break; + case SmMlAttributeValueMovablelimits::MlTrue: + addAttribute(XML_MOVABLELIMITS, XML_TRUE); + break; + default: + declareMlError(); + break; + } + break; + } + case SmMlAttributeValueType::MlRspace: + { + auto aSizeData = aAttribute.getMlRspace(); + auto aLengthData = aSizeData->m_aLengthValue; + exportMlAttributeLength(XML_RSPACE, aLengthData); + break; + } + case SmMlAttributeValueType::MlSeparator: + { + auto aAttributeValue = aAttribute.getMlSeparator(); + switch (aAttributeValue->m_aSeparator) + { + case SmMlAttributeValueSeparator::MlFalse: + addAttribute(XML_SEPARATOR, XML_FALSE); + break; + case SmMlAttributeValueSeparator::MlTrue: + addAttribute(XML_SEPARATOR, XML_TRUE); + break; + default: + declareMlError(); + break; + } + break; + } + case SmMlAttributeValueType::MlStretchy: + { + auto aAttributeValue = aAttribute.getMlStretchy(); + switch (aAttributeValue->m_aStretchy) + { + case SmMlAttributeValueStretchy::MlFalse: + addAttribute(XML_STRETCHY, XML_FALSE); + break; + case SmMlAttributeValueStretchy::MlTrue: + addAttribute(XML_STRETCHY, XML_TRUE); + break; + default: + declareMlError(); + break; + } + break; + } + case SmMlAttributeValueType::MlSymmetric: + { + auto aAttributeValue = aAttribute.getMlSymmetric(); + switch (aAttributeValue->m_aSymmetric) + { + case SmMlAttributeValueSymmetric::MlFalse: + addAttribute(XML_SYMMETRIC, XML_FALSE); + break; + case SmMlAttributeValueSymmetric::MlTrue: + addAttribute(XML_SYMMETRIC, XML_TRUE); + break; + default: + declareMlError(); + break; + } + break; + } + default: + declareMlError(); + break; + } + } +} + +SvXMLElementExport* SmMLExport::exportMlElement(const SmMlElement* pMlElement) +{ + SvXMLElementExport* pElementExport; + switch (pMlElement->getMlElementType()) + { + case SmMlElementType::MlMath: + pElementExport = createElementExport(XML_MATH); + break; + case SmMlElementType::MlMi: + pElementExport = createElementExport(XML_MI); + break; + case SmMlElementType::MlMerror: + pElementExport = createElementExport(XML_MERROR); + break; + case SmMlElementType::MlMn: + pElementExport = createElementExport(XML_MN); + break; + case SmMlElementType::MlMo: + pElementExport = createElementExport(XML_MO); + break; + case SmMlElementType::MlMrow: + pElementExport = createElementExport(XML_MROW); + break; + case SmMlElementType::MlMtext: + pElementExport = createElementExport(XML_MTEXT); + break; + case SmMlElementType::MlMstyle: + pElementExport = createElementExport(XML_MSTYLE); + break; + default: + pElementExport = nullptr; + } + const OUString& aElementText = pMlElement->getText(); + exportMlAttributes(pMlElement); + if (aElementText.isEmpty()) + GetDocHandler()->characters(aElementText); + return pElementExport; +} + +namespace +{ +struct exportMlElementTreeExecData +{ +private: + SmMLExport* m_pSmMLExport; + std::vector<SvXMLElementExport*> m_aSvXMLElementExportList; + size_t m_nDepth; + +public: + inline exportMlElementTreeExecData(SmMLExport* pSmMLExport) + : m_pSmMLExport(pSmMLExport) + , m_aSvXMLElementExportList(1024) + , m_nDepth(0) + { + } + + inline void deleteDepthData() + { + delete m_aSvXMLElementExportList[m_nDepth]; + --m_nDepth; + } + + inline void setDepthData(SvXMLElementExport* aSvXMLElementExportList) + { + if (m_nDepth == m_aSvXMLElementExportList.size()) + m_aSvXMLElementExportList.resize(m_aSvXMLElementExportList.size() + 1024); + m_aSvXMLElementExportList[m_nDepth] = aSvXMLElementExportList; + } + + inline void incrementDepth() { ++m_nDepth; } + + inline SmMLExport* getSmMLExport() { return m_pSmMLExport; }; +}; + +} // end unnamed namespace + +static inline void exportMlElementTreeExec(SmMlElement* aSmMlElement, void* aData) +{ + // Prepare data + exportMlElementTreeExecData* pData = static_cast<exportMlElementTreeExecData*>(aData); + pData->setDepthData(pData->getSmMLExport()->exportMlElement(aSmMlElement)); + + // Prepare for following + // If it has sub elements, then it will be the next + if (aSmMlElement->getSubElementsCount() != 0) + pData->incrementDepth(); + else // Otherwise remounts up to where it should be + { + while (aSmMlElement->getParentElement() != nullptr) + { + // get parent + SmMlElement* pParent = aSmMlElement->getParentElement(); + pData->deleteDepthData(); + // was this the last branch ? + if (aSmMlElement->getSubElementId() + 1 != pParent->getSubElementsCount()) // yes -> up + break; // no -> stop going up + // Prepare for next round + aSmMlElement = pParent; + } + } +} + +void SmMLExport::exportMlElementTree() +{ + exportMlElementTreeExecData* aData = new exportMlElementTreeExecData(this); + mathml::SmMlIteratorTopToBottom(m_pElementTree, exportMlElementTreeExec, aData); + delete aData; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/mathml/import.cxx b/starmath/source/mathml/import.cxx new file mode 100644 index 0000000000..d857e56930 --- /dev/null +++ b/starmath/source/mathml/import.cxx @@ -0,0 +1,1405 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +// Our mathml +#include <mathml/import.hxx> + +// LO tools to use +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/document/XDocumentPropertiesSupplier.hpp> +#include <com/sun/star/embed/ElementModes.hpp> +#include <com/sun/star/packages/WrongPasswordException.hpp> +#include <com/sun/star/packages/zip/ZipIOException.hpp> +#include <com/sun/star/task/XStatusIndicator.hpp> +#include <com/sun/star/xml/sax/FastParser.hpp> +#include <com/sun/star/xml/sax/InputSource.hpp> +#include <com/sun/star/xml/sax/Parser.hpp> +#include <com/sun/star/xml/sax/SAXParseException.hpp> + +// Extra LO tools +#include <comphelper/fileformat.h> +#include <comphelper/genericpropertyset.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/propertysetinfo.hxx> +#include <comphelper/servicehelper.hxx> +#include <rtl/character.hxx> +#include <sfx2/docfile.hxx> +#include <sfx2/frame.hxx> +#include <sfx2/sfxsids.hrc> +#include <sot/storage.hxx> +#include <svtools/sfxecode.hxx> +#include <svl/itemset.hxx> +#include <svl/stritem.hxx> +#include <svx/dialmgr.hxx> +#include <svx/strings.hrc> +#include <unotools/streamwrap.hxx> +#include <xmloff/DocumentSettingsContext.hxx> +#include <xmloff/xmlmetai.hxx> +#include <xmloff/xmlnamespace.hxx> +#include <xmloff/xmltoken.hxx> +#include <o3tl/string_view.hxx> + +// Our starmath tools +#include <cfgitem.hxx> +#include <document.hxx> +#include <xparsmlbase.hxx> +#include <smmod.hxx> +#include <starmathdatabase.hxx> +#include <unomodel.hxx> + +// Old parser +#include <mathmlimport.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::document; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::uno; +using namespace com::sun::star::xml::sax; +using namespace ::xmloff::token; + +// SmMLImportContext +/*************************************************************************************************/ + +SmMlElement* SmMLImportWrapper::getElementTree() +{ + return m_pMlImport == nullptr ? nullptr : m_pMlImport->getElementTree(); +} + +ErrCode SmMLImportWrapper::Import(SfxMedium& rMedium) +{ + // Fetch context + uno::Reference<uno::XComponentContext> xContext(comphelper::getProcessComponentContext()); + if (!xContext.is()) + { + SAL_WARN("starmath", "Failed to fetch model while file input"); + return ERRCODE_SFX_DOLOADFAILED; + } + + // Check model + if (!m_xModel.is()) + { + SAL_WARN("starmath", "Failed to fetch model while file input"); + return ERRCODE_SFX_DOLOADFAILED; + } + + // Try to get an XStatusIndicator from the Medium + uno::Reference<task::XStatusIndicator> xStatusIndicator; + + // Get model via uno + SmModel* pModel = m_xModel.get(); + if (pModel == nullptr) + { + SAL_WARN("starmath", "Failed to fetch sm model while file input"); + return ERRCODE_SFX_DOLOADFAILED; + } + + // Get doc shell + m_pDocShell = static_cast<SmDocShell*>(pModel->GetObjectShell()); + if (m_pDocShell == nullptr) + { + SAL_WARN("starmath", "Failed to fetch smdoc shell while file input"); + return ERRCODE_SFX_DOLOADFAILED; + } + + // Check if it is an embed object + bool bEmbedded = m_pDocShell->GetCreateMode() == SfxObjectCreateMode::EMBEDDED; + + if (!bEmbedded) + { + // Extra check to ensure everything is fine + if (m_pDocShell->GetMedium() != &rMedium) + { + SAL_WARN("starmath", "Given medium and doc shell medium differ while file input"); + return ERRCODE_SFX_DOLOADFAILED; + } + + // Fetch the item set + const SfxUnoAnyItem* pItem = rMedium.GetItemSet().GetItem(SID_PROGRESS_STATUSBAR_CONTROL); + if (pItem != nullptr) + pItem->GetValue() >>= xStatusIndicator; + } + + // Create property list + static const comphelper::PropertyMapEntry aInfoMap[] + = { { u"PrivateData"_ustr, 0, cppu::UnoType<XInterface>::get(), + beans::PropertyAttribute::MAYBEVOID, 0 }, + { u"BaseURI"_ustr, 0, ::cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::MAYBEVOID, 0 }, + { u"StreamRelPath"_ustr, 0, ::cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::MAYBEVOID, 0 }, + { u"StreamName"_ustr, 0, ::cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::MAYBEVOID, 0 } }; + uno::Reference<beans::XPropertySet> xInfoSet( + comphelper::GenericPropertySet_CreateInstance(new comphelper::PropertySetInfo(aInfoMap))); + + // Set base URI + // needed for relative URLs; but it's OK to import e.g. MathML from the clipboard without one + SAL_INFO_IF(rMedium.GetBaseURL().isEmpty(), "starmath", "SmMLImportWrapper: no base URL"); + xInfoSet->setPropertyValue("BaseURI", Any(rMedium.GetBaseURL())); + + // Fetch progress range + sal_Int32 nProgressRange(rMedium.IsStorage() ? 3 : 1); + if (xStatusIndicator.is()) + { + xStatusIndicator->start(SvxResId(RID_SVXSTR_DOC_LOAD), nProgressRange); + xStatusIndicator->setValue(0); + } + + // Get storage + if (rMedium.IsStorage()) + { + // TODO/LATER: handle the case of embedded links gracefully + if (bEmbedded) // && !rMedium.GetStorage()->IsRoot() ) + { + OUString aName(u"dummyObjName"_ustr); + const SfxStringItem* pDocHierarchItem + = rMedium.GetItemSet().GetItem(SID_DOC_HIERARCHICALNAME); + if (pDocHierarchItem != nullptr) + aName = pDocHierarchItem->GetValue(); + + if (!aName.isEmpty()) + xInfoSet->setPropertyValue("StreamRelPath", Any(aName)); + } + + // Check if use OASIS ( new document format ) + bool bOASIS = SotStorage::GetVersion(rMedium.GetStorage()) > SOFFICE_FILEFORMAT_60; + if (xStatusIndicator.is()) + xStatusIndicator->setValue(1); + + // Error code in case of needed + ErrCode nWarn = ERRCODE_NONE; + + // Read metadata + // read a component from storage + if (!bEmbedded) + { + if (bOASIS) + nWarn = ReadThroughComponentS(rMedium.GetStorage(), m_xModel, u"meta.xml", xContext, + xInfoSet, + u"com.sun.star.comp.Math.MLOasisMetaImporter", 6); + else + nWarn + = ReadThroughComponentS(rMedium.GetStorage(), m_xModel, u"meta.xml", xContext, + xInfoSet, u"com.sun.star.comp.Math.XMLMetaImporter", 5); + } + + // Check if successful + if (nWarn != ERRCODE_NONE) + { + if (xStatusIndicator.is()) + xStatusIndicator->end(); + SAL_WARN("starmath", "Failed to read file"); + return nWarn; + } + + // Increase success indicator + if (xStatusIndicator.is()) + xStatusIndicator->setValue(2); + + // Read settings + // read a component from storage + if (bOASIS) + nWarn = ReadThroughComponentS(rMedium.GetStorage(), m_xModel, u"settings.xml", xContext, + xInfoSet, + u"com.sun.star.comp.Math.MLOasisSettingsImporter", 6); + else + nWarn + = ReadThroughComponentS(rMedium.GetStorage(), m_xModel, u"settings.xml", xContext, + xInfoSet, u"com.sun.star.comp.Math.XMLSettingsImporter", 5); + + // Check if successful + if (nWarn != ERRCODE_NONE) + { + if (xStatusIndicator.is()) + xStatusIndicator->end(); + SAL_WARN("starmath", "Failed to read file"); + return nWarn; + } + + // Increase success indicator + if (xStatusIndicator.is()) + xStatusIndicator->setValue(3); + + // Read document + // read a component from storage + if (m_pDocShell->GetSmSyntaxVersion() == 5) + nWarn = ReadThroughComponentS(rMedium.GetStorage(), m_xModel, u"content.xml", xContext, + xInfoSet, u"com.sun.star.comp.Math.XMLImporter", 5); + else + nWarn = ReadThroughComponentS(rMedium.GetStorage(), m_xModel, u"content.xml", xContext, + xInfoSet, u"com.sun.star.comp.Math.MLImporter", 6); + // Check if successful + if (nWarn != ERRCODE_NONE) + { + if (xStatusIndicator.is()) + xStatusIndicator->end(); + SAL_WARN("starmath", "Failed to read file"); + return nWarn; + } + + // Finish + if (xStatusIndicator.is()) + xStatusIndicator->end(); + return ERRCODE_NONE; + } + else + { + // Create input stream + Reference<io::XInputStream> xInputStream + = new utl::OInputStreamWrapper(rMedium.GetInStream()); + + // Increase success indicator + if (xStatusIndicator.is()) + xStatusIndicator->setValue(1); + + // Read data + // read a component from input stream + ErrCode nError = ERRCODE_NONE; + if (m_pDocShell->GetSmSyntaxVersion() == 5) + nError = ReadThroughComponentIS(xInputStream, m_xModel, xContext, xInfoSet, + u"com.sun.star.comp.Math.XMLImporter", false, 5); + else + nError = ReadThroughComponentIS(xInputStream, m_xModel, xContext, xInfoSet, + u"com.sun.star.comp.Math.MLImporter", false, 6); + + // Finish + if (xStatusIndicator.is()) + xStatusIndicator->end(); + + // Declare any error + if (nError != ERRCODE_NONE) + SAL_WARN("starmath", "Failed to read file"); + + return nError; + } +} + +ErrCode SmMLImportWrapper::Import(std::u16string_view aSource) +{ + // Fetch context + uno::Reference<uno::XComponentContext> xContext(comphelper::getProcessComponentContext()); + if (!xContext.is()) + { + SAL_WARN("starmath", "Failed to fetch model while file input"); + return ERRCODE_SFX_DOLOADFAILED; + } + + // Check model + if (!m_xModel.is()) + { + SAL_WARN("starmath", "Failed to fetch model while file input"); + return ERRCODE_SFX_DOLOADFAILED; + } + + // Make a model component from our SmModel + uno::Reference<lang::XComponent> xModelComp = m_xModel; + if (!xModelComp.is()) + { + SAL_WARN("starmath", "Failed to make model while file input"); + return ERRCODE_SFX_DOLOADFAILED; + } + + // Get model via uno + SmModel* pModel = m_xModel.get(); + if (pModel == nullptr) + { + SAL_WARN("starmath", "Failed to fetch sm model while file input"); + return ERRCODE_SFX_DOLOADFAILED; + } + + // Get doc shell + m_pDocShell = static_cast<SmDocShell*>(pModel->GetObjectShell()); + if (m_pDocShell == nullptr) + { + SAL_WARN("starmath", "Failed to fetch smdoc shell while file input"); + return ERRCODE_SFX_DOLOADFAILED; + } + + // Create property list + static const comphelper::PropertyMapEntry aInfoMap[] + = { { u"PrivateData"_ustr, 0, cppu::UnoType<XInterface>::get(), + beans::PropertyAttribute::MAYBEVOID, 0 }, + { u"BaseURI"_ustr, 0, ::cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::MAYBEVOID, 0 }, + { u"StreamRelPath"_ustr, 0, ::cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::MAYBEVOID, 0 }, + { u"StreamName"_ustr, 0, ::cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::MAYBEVOID, 0 } }; + uno::Reference<beans::XPropertySet> xInfoSet( + comphelper::GenericPropertySet_CreateInstance(new comphelper::PropertySetInfo(aInfoMap))); + + // Read data + // read a component from text + ErrCode nError = ReadThroughComponentMS(aSource, xModelComp, xContext, xInfoSet); + + // Declare any error + if (nError != ERRCODE_NONE) + { + SAL_WARN("starmath", "Failed to read file"); + return nError; + } + + return ERRCODE_NONE; +} + +// read a component from input stream +ErrCode SmMLImportWrapper::ReadThroughComponentIS( + const Reference<io::XInputStream>& xInputStream, const Reference<XComponent>& xModelComponent, + Reference<uno::XComponentContext> const& rxContext, + Reference<beans::XPropertySet> const& rPropSet, const char16_t* pFilterName, bool bEncrypted, + int_fast16_t nSyntaxVersion) +{ + // Needs an input stream but checked by caller + // Needs a context but checked by caller + // Needs property set but checked by caller + // Needs a filter name but checked by caller + + // Prepare ParserInputSource + xml::sax::InputSource aParserInput; + aParserInput.aInputStream = xInputStream; + + // Prepare property list + Sequence<Any> aArgs{ Any(rPropSet) }; + + // Get filter + Reference<XInterface> xFilter + = rxContext->getServiceManager()->createInstanceWithArgumentsAndContext( + OUString(pFilterName), aArgs, rxContext); + if (!xFilter.is()) + { + SAL_WARN("starmath", "Can't instantiate filter component " << OUString(pFilterName)); + return ERRCODE_SFX_DOLOADFAILED; + } + + // Connect model and filter + Reference<XImporter> xImporter(xFilter, UNO_QUERY); + xImporter->setTargetDocument(xModelComponent); + + // Finally, parser the stream + try + { + Reference<css::xml::sax::XFastParser> xFastParser(xFilter, UNO_QUERY); + Reference<css::xml::sax::XFastDocumentHandler> xFastDocHandler(xFilter, UNO_QUERY); + if (xFastParser) + { + xFastParser->setCustomEntityNames(starmathdatabase::icustomMathmlHtmlEntities); + xFastParser->parseStream(aParserInput); + } + else if (xFastDocHandler) + { + Reference<css::xml::sax::XFastParser> xParser + = css::xml::sax::FastParser::create(rxContext); + xParser->setCustomEntityNames(starmathdatabase::icustomMathmlHtmlEntities); + xParser->setFastDocumentHandler(xFastDocHandler); + xParser->parseStream(aParserInput); + } + else + { + Reference<css::xml::sax::XDocumentHandler> xDocHandler(xFilter, UNO_QUERY); + assert(xDocHandler); + Reference<css::xml::sax::XParser> xParser = css::xml::sax::Parser::create(rxContext); + xParser->setDocumentHandler(xDocHandler); + xParser->parseStream(aParserInput); + } + + if (nSyntaxVersion == 5) + { + SmXMLImport* pXMlImport = dynamic_cast<SmXMLImport*>(xFilter.get()); + if (pXMlImport != nullptr && pXMlImport->GetSuccess()) + return ERRCODE_NONE; + else + { + SAL_WARN("starmath", "Filter failed on file input"); + // However this can not be included since it's not public + if (pXMlImport == nullptr) + return ERRCODE_NONE; + return ERRCODE_SFX_DOLOADFAILED; + } + } + + m_pMlImport = dynamic_cast<SmMLImport*>(xFilter.get()); + if (m_pMlImport != nullptr && m_pMlImport->getSuccess()) + return ERRCODE_NONE; + else + { + SAL_WARN("starmath", "Filter failed on file input"); + return ERRCODE_SFX_DOLOADFAILED; + } + } + catch (const xml::sax::SAXParseException& r) + { + // Sax parser sends wrapped exceptions, try to find the original one + xml::sax::SAXException aTmp; + xml::sax::SAXException aSaxEx = *static_cast<const xml::sax::SAXException*>(&r); + while (aSaxEx.WrappedException >>= aTmp) + aSaxEx = aTmp; + + packages::zip::ZipIOException aBrokenPackage; + if (aSaxEx.WrappedException >>= aBrokenPackage) + { + SAL_WARN("starmath", "Failed to read file SAXParseException"); + return ERRCODE_IO_BROKENPACKAGE; + } + + if (bEncrypted) + { + SAL_WARN("starmath", "Wrong file password SAXParseException"); + return ERRCODE_SFX_WRONGPASSWORD; + } + } + catch (const xml::sax::SAXException& r) + { + packages::zip::ZipIOException aBrokenPackage; + if (r.WrappedException >>= aBrokenPackage) + { + SAL_WARN("starmath", "Failed to read file SAXException"); + return ERRCODE_IO_BROKENPACKAGE; + } + + if (bEncrypted) + { + SAL_WARN("starmath", "Wrong file password SAXException"); + return ERRCODE_SFX_WRONGPASSWORD; + } + } + catch (const packages::zip::ZipIOException&) + { + SAL_WARN("starmath", "Failed to unzip file ZipIOException"); + return ERRCODE_IO_BROKENPACKAGE; + } + catch (const io::IOException&) + { + SAL_WARN("starmath", "Failed to read file ZipIOException"); + return ERRCODE_IO_UNKNOWN; + } + catch (const std::range_error&) + { + SAL_WARN("starmath", "Failed to read file"); + return ERRCODE_ABORT; + } + + return ERRCODE_ABORT; +} + +// read a component from storage +ErrCode SmMLImportWrapper::ReadThroughComponentS(const uno::Reference<embed::XStorage>& xStorage, + const Reference<XComponent>& xModelComponent, + const char16_t* pStreamName, + Reference<uno::XComponentContext> const& rxContext, + Reference<beans::XPropertySet> const& rPropSet, + const char16_t* pFilterName, + int_fast16_t nSyntaxVersion) +{ + // Needs a storage but checked by caller + // Needs a model but checked by caller + // Needs a stream name but checked by caller + // Needs a context but checked by caller + // Needs a property set but checked by caller + // Needs a filter name but checked by caller + + // Get the input stream + try + { + // Create the stream for the event read + uno::Reference<io::XStream> xEventsStream + = xStorage->openStreamElement(OUString(pStreamName), embed::ElementModes::READ); + + // Determine if stream is encrypted or not + uno::Reference<beans::XPropertySet> xProps(xEventsStream, uno::UNO_QUERY); + Any aAny = xProps->getPropertyValue("Encrypted"); + bool bEncrypted = false; + aAny >>= bEncrypted; + + // Set base URL and open stream + rPropSet->setPropertyValue("StreamName", Any(OUString(pStreamName))); + Reference<io::XInputStream> xStream = xEventsStream->getInputStream(); + + // Execute read + return ReadThroughComponentIS(xStream, xModelComponent, rxContext, rPropSet, pFilterName, + bEncrypted, nSyntaxVersion); + } + catch (packages::WrongPasswordException&) + { + SAL_WARN("starmath", "Wrong file password"); + return ERRCODE_SFX_WRONGPASSWORD; + } + catch (packages::zip::ZipIOException&) + { + SAL_WARN("starmath", "Failed to unzip file"); + return ERRCODE_IO_BROKENPACKAGE; + } + catch (uno::Exception&) + { + } + + return ERRCODE_SFX_DOLOADFAILED; +} + +// read a component from text +ErrCode SmMLImportWrapper::ReadThroughComponentMS( + std::u16string_view aText, const css::uno::Reference<css::lang::XComponent>& xModelComponent, + css::uno::Reference<css::uno::XComponentContext> const& rxContext, + css::uno::Reference<css::beans::XPropertySet> const& rPropSet) +{ + // Needs a storage but checked by caller + // Needs a model but checked by caller + // Needs a stream name but checked by caller + // Needs a context but checked by caller + // Needs a property set but checked by caller + // Needs a filter name but checked by caller + + // Get the input stream + try + { + // Generate input memory stream + SvMemoryStream aMemoryStream; + aMemoryStream.WriteOString(OUStringToOString(aText, RTL_TEXTENCODING_UTF8)); + uno::Reference<io::XInputStream> xStream(new utl::OInputStreamWrapper(aMemoryStream)); + + // Execute read + return ReadThroughComponentIS(xStream, xModelComponent, rxContext, rPropSet, + u"com.sun.star.comp.Math.MLImporter", false, 6); + } + catch (packages::WrongPasswordException&) + { + SAL_WARN("starmath", "Wrong file password"); + return ERRCODE_SFX_WRONGPASSWORD; + } + catch (packages::zip::ZipIOException&) + { + SAL_WARN("starmath", "Failed to unzip file"); + return ERRCODE_IO_BROKENPACKAGE; + } + catch (uno::Exception&) + { + } + + return ERRCODE_SFX_DOLOADFAILED; +} + +// SmMLImport technical +/*************************************************************************************************/ + +extern "C" SAL_DLLPUBLIC_EXPORT uno::XInterface* +Math_MLImporter_get_implementation(uno::XComponentContext* pCtx, + uno::Sequence<uno::Any> const& /*rSeq*/) +{ + return cppu::acquire( + new SmMLImport(pCtx, "com.sun.star.comp.Math.XMLImporter", SvXMLImportFlags::ALL)); +} + +extern "C" SAL_DLLPUBLIC_EXPORT uno::XInterface* +Math_MLOasisMetaImporter_get_implementation(uno::XComponentContext* pCtx, + uno::Sequence<uno::Any> const& /*rSeq*/) +{ + return cppu::acquire(new SmMLImport(pCtx, "com.sun.star.comp.Math.XMLOasisMetaImporter", + SvXMLImportFlags::META)); +} + +extern "C" SAL_DLLPUBLIC_EXPORT uno::XInterface* +Math_MLOasisSettingsImporter_get_implementation(uno::XComponentContext* pCtx, + uno::Sequence<uno::Any> const& /*rSeq*/) +{ + return cppu::acquire(new SmMLImport(pCtx, "com.sun.star.comp.Math.XMLOasisSettingsImporter", + SvXMLImportFlags::SETTINGS)); +} + +// SmMLImportContext +/*************************************************************************************************/ + +namespace +{ +class SmMLImportContext : public SvXMLImportContext +{ +private: + SmMlElement** m_pParent; + SmMlElement* m_pElement; + SmMlElement* m_pStyle; + +public: + SmMLImportContext(SmMLImport& rImport, SmMlElement** pParent) + : SvXMLImportContext(rImport) + , m_pParent(pParent) + , m_pElement(nullptr) + , m_pStyle(nullptr) + { + } + +private: + void declareMlError(); + +public: + /** Handles characters (text) + */ + virtual void SAL_CALL characters(const OUString& aChars) override; + + /** Starts the mathml element + */ + virtual void SAL_CALL startFastElement( + sal_Int32 nElement, const Reference<XFastAttributeList>& aAttributeList) override; + + /** Ends the mathml element + */ + virtual void SAL_CALL endFastElement(sal_Int32 Element) override; + + /** Creates child element + */ + virtual uno::Reference<XFastContextHandler> + SAL_CALL createFastChildContext(sal_Int32 nElement, + const uno::Reference<XFastAttributeList>& Attribs) override; + + /** Inherits the style from it's parents + */ + void inheritStyle(); + + /** Inherits the style from it's parents on end + */ + void inheritStyleEnd(); + + /** Handle mathml attributes + */ + void handleAttributes(const Reference<XFastAttributeList>& aAttributeList); + + /** Handle mathml length attributes + */ + SmLengthValue handleLengthAttribute(const OUString& aAttribute); +}; + +uno::Reference<XFastContextHandler> SAL_CALL +SmMLImportContext::createFastChildContext(sal_Int32, const uno::Reference<XFastAttributeList>&) +{ + uno::Reference<xml::sax::XFastContextHandler> xContext; + xContext = new SmMLImportContext(static_cast<SmMLImport&>(GetImport()), &m_pElement); + return xContext; +} + +void SmMLImportContext::declareMlError() +{ + SmMLImport& aSmMLImport = static_cast<SmMLImport&>(GetImport()); + aSmMLImport.declareMlError(); +} + +void SmMLImportContext::inheritStyle() +{ + while ((m_pStyle = m_pStyle->getParentElement()) != nullptr) + { + if (m_pStyle->getParentElement()->getMlElementType() == SmMlElementType::MlMstyle + || m_pStyle->getParentElement()->getMlElementType() == SmMlElementType::MlMath) + break; + } + + // Parent inheritation + // Mathcolor, mathsize, dir and displaystyle are inherited from parent + SmMlElement* pParent = *m_pParent; + m_pElement->setAttribute(pParent->getAttribute(SmMlAttributeValueType::MlMathcolor)); + m_pElement->setAttribute(pParent->getAttribute(SmMlAttributeValueType::MlMathsize)); + m_pElement->setAttribute(pParent->getAttribute(SmMlAttributeValueType::MlDir)); + m_pElement->setAttribute(pParent->getAttribute(SmMlAttributeValueType::MlDisplaystyle)); + + // Inherit operator dictionary overwrites + if (m_pStyle != nullptr + && (m_pElement->getMlElementType() == SmMlElementType::MlMo + || m_pElement->getMlElementType() == SmMlElementType::MlMstyle + || m_pElement->getMlElementType() == SmMlElementType::MlMath)) + { + // TODO fetch operator dictionary first and then overwrite + if (m_pStyle->isAttributeSet(SmMlAttributeValueType::MlAccent)) + m_pElement->setAttribute(m_pStyle->getAttribute(SmMlAttributeValueType::MlAccent)); + if (m_pStyle->isAttributeSet(SmMlAttributeValueType::MlFence)) + m_pElement->setAttribute(m_pStyle->getAttribute(SmMlAttributeValueType::MlFence)); + if (m_pStyle->isAttributeSet(SmMlAttributeValueType::MlLspace)) + m_pElement->setAttribute(m_pStyle->getAttribute(SmMlAttributeValueType::MlLspace)); + if (m_pStyle->isAttributeSet(SmMlAttributeValueType::MlMaxsize)) + m_pElement->setAttribute(m_pStyle->getAttribute(SmMlAttributeValueType::MlMaxsize)); + if (m_pStyle->isAttributeSet(SmMlAttributeValueType::MlMinsize)) + m_pElement->setAttribute(m_pStyle->getAttribute(SmMlAttributeValueType::MlMinsize)); + if (m_pStyle->isAttributeSet(SmMlAttributeValueType::MlMovablelimits)) + m_pElement->setAttribute( + m_pStyle->getAttribute(SmMlAttributeValueType::MlMovablelimits)); + if (m_pStyle->isAttributeSet(SmMlAttributeValueType::MlRspace)) + m_pElement->setAttribute(m_pStyle->getAttribute(SmMlAttributeValueType::MlRspace)); + if (m_pStyle->isAttributeSet(SmMlAttributeValueType::MlSeparator)) + m_pElement->setAttribute(m_pStyle->getAttribute(SmMlAttributeValueType::MlSeparator)); + if (m_pStyle->isAttributeSet(SmMlAttributeValueType::MlStretchy)) + m_pElement->setAttribute(m_pStyle->getAttribute(SmMlAttributeValueType::MlStretchy)); + if (m_pStyle->isAttributeSet(SmMlAttributeValueType::MlSymmetric)) + m_pElement->setAttribute(m_pStyle->getAttribute(SmMlAttributeValueType::MlSymmetric)); + + if (m_pElement->getMlElementType() == SmMlElementType::MlMo) + { + // Set form based in position + SmMlAttribute aAttribute(SmMlAttributeValueType::MlForm); + SmMlForm aForm; + if (m_pElement->getSubElementId() == 0) + aForm = { SmMlAttributeValueForm::MlPrefix }; + else + aForm = { SmMlAttributeValueForm::MlInfix }; + aAttribute.setMlForm(&aForm); + m_pElement->setAttribute(aAttribute); + } + } + + // Inherit mathvariant + if (m_pStyle && m_pStyle->isAttributeSet(SmMlAttributeValueType::MlMathvariant)) + m_pElement->setAttribute(m_pStyle->getAttribute(SmMlAttributeValueType::MlMathvariant)); +} + +void SmMLImportContext::inheritStyleEnd() +{ + // Mo: check it is the end: postfix + if (m_pElement->getMlElementType() == SmMlElementType::MlMo) + { + if ((*m_pParent)->getSubElementsCount() == m_pElement->getSubElementId()) + { + // Set form based in position + SmMlAttribute aAttribute(SmMlAttributeValueType::MlForm); + SmMlForm aForm = { SmMlAttributeValueForm::MlPosfix }; + aAttribute.setMlForm(&aForm); + m_pElement->setAttribute(aAttribute); + } + } + + // Mi: 1 char -> italic + if (m_pElement->getMlElementType() != SmMlElementType::MlMi) + return; + + // Inherit mathvariant + if (!m_pStyle->isAttributeSet(SmMlAttributeValueType::MlMathvariant)) + { + sal_Int32 nIndexUtf16 = 0; + // Check if there is only one code point + m_pElement->getText().iterateCodePoints(&nIndexUtf16, 1); + // Mathml says that 1 code point -> italic + if (nIndexUtf16 == m_pElement->getText().getLength()) + { + SmMlAttribute aAttribute(SmMlAttributeValueType::MlMathvariant); + SmMlMathvariant aMathvariant = { SmMlAttributeValueMathvariant::italic }; + aAttribute.setMlMathvariant(&aMathvariant); + aAttribute.setSet(false); + m_pElement->setAttribute(aAttribute); + } + } +} + +SmLengthValue SmMLImportContext::handleLengthAttribute(const OUString& aAttribute) +{ + // Locate unit indication + int32_t nUnitPos; + for (nUnitPos = 0; + nUnitPos < aAttribute.getLength() + && (rtl::isAsciiHexDigit(aAttribute[nUnitPos]) || aAttribute[nUnitPos] == '.'); + ++nUnitPos) + ; + + // Find unit + SmLengthUnit nUnit = SmLengthUnit::MlM; + if (nUnitPos != aAttribute.getLength()) + { + OUString aUnit = aAttribute.copy(nUnitPos); + if (aUnit.compareToIgnoreAsciiCaseAscii("ex")) + nUnit = SmLengthUnit::MlEx; + if (aUnit.compareToIgnoreAsciiCaseAscii("px")) + nUnit = SmLengthUnit::MlPx; + if (aUnit.compareToIgnoreAsciiCaseAscii("in")) + nUnit = SmLengthUnit::MlIn; + if (aUnit.compareToIgnoreAsciiCaseAscii("cm")) + nUnit = SmLengthUnit::MlCm; + if (aUnit.compareToIgnoreAsciiCaseAscii("mm")) + nUnit = SmLengthUnit::MlMm; + if (aUnit.compareToIgnoreAsciiCaseAscii("pt")) + nUnit = SmLengthUnit::MlPt; + if (aUnit.compareToIgnoreAsciiCaseAscii("pc")) + nUnit = SmLengthUnit::MlPc; + if (aUnit.compareToIgnoreAsciiCaseAscii("%")) + nUnit = SmLengthUnit::MlP; + else + declareMlError(); + } + + // Get value + std::u16string_view aValue = aAttribute.subView(0, nUnitPos); + double nValue = o3tl::toDouble(aValue); + if (nValue == 0) + { + nUnit = SmLengthUnit::MlM; + nValue = 1.0; + declareMlError(); + } + + // Return + SmLengthValue aLengthValue = { nUnit, nValue, new OUString(aAttribute) }; + return aLengthValue; +} + +void SmMLImportContext::handleAttributes(const Reference<XFastAttributeList>& aAttributeList) +{ + for (auto& aIter : sax_fastparser::castToFastAttributeList(aAttributeList)) + { + SmMlAttribute aAttribute(SmMlAttributeValueType::NMlEmpty); + switch (aIter.getToken() & TOKEN_MASK) + { + case XML_ACCENT: + { + if (IsXMLToken(aIter, XML_TRUE)) + { + aAttribute.setMlAttributeValueType(SmMlAttributeValueType::MlAccent); + SmMlAccent aAccent = { SmMlAttributeValueAccent::MlTrue }; + aAttribute.setMlAccent(&aAccent); + } + else if (IsXMLToken(aIter, XML_FALSE)) + { + aAttribute.setMlAttributeValueType(SmMlAttributeValueType::MlAccent); + SmMlAccent aAccent = { SmMlAttributeValueAccent::MlFalse }; + aAttribute.setMlAccent(&aAccent); + } + else + { + declareMlError(); + } + break; + } + case XML_DIR: + { + if (IsXMLToken(aIter, XML_RTL)) + { + aAttribute.setMlAttributeValueType(SmMlAttributeValueType::MlDir); + SmMlDir aDir = { SmMlAttributeValueDir::MlRtl }; + aAttribute.setMlDir(&aDir); + } + else if (IsXMLToken(aIter, XML_LTR)) + { + aAttribute.setMlAttributeValueType(SmMlAttributeValueType::MlDir); + SmMlDir aDir = { SmMlAttributeValueDir::MlLtr }; + aAttribute.setMlDir(&aDir); + } + else + { + declareMlError(); + } + break; + } + case XML_DISPLAYSTYLE: + if (IsXMLToken(aIter, XML_TRUE)) + { + aAttribute.setMlAttributeValueType(SmMlAttributeValueType::MlDisplaystyle); + SmMlDisplaystyle aDisplaystyle = { SmMlAttributeValueDisplaystyle::MlTrue }; + aAttribute.setMlDisplaystyle(&aDisplaystyle); + } + else if (IsXMLToken(aIter, XML_FALSE)) + { + aAttribute.setMlAttributeValueType(SmMlAttributeValueType::MlDisplaystyle); + SmMlDisplaystyle aDisplaystyle = { SmMlAttributeValueDisplaystyle::MlFalse }; + aAttribute.setMlDisplaystyle(&aDisplaystyle); + } + else + { + declareMlError(); + } + break; + case XML_FENCE: + if (IsXMLToken(aIter, XML_TRUE)) + { + aAttribute.setMlAttributeValueType(SmMlAttributeValueType::MlFence); + SmMlFence aFence = { SmMlAttributeValueFence::MlTrue }; + aAttribute.setMlFence(&aFence); + } + else if (IsXMLToken(aIter, XML_FALSE)) + { + aAttribute.setMlAttributeValueType(SmMlAttributeValueType::MlFence); + SmMlFence aFence = { SmMlAttributeValueFence::MlFalse }; + aAttribute.setMlFence(&aFence); + } + else + { + declareMlError(); + } + break; + case XML_HREF: + { + aAttribute.setMlAttributeValueType(SmMlAttributeValueType::MlHref); + OUString* aRef = new OUString(aIter.toString()); + SmMlHref aHref = { SmMlAttributeValueHref::NMlValid, aRef }; + aAttribute.setMlHref(&aHref); + break; + } + case XML_LSPACE: + { + SmMlLspace aLspace; + aLspace.m_aLengthValue = handleLengthAttribute(aIter.toString()); + aAttribute.setMlLspace(&aLspace); + break; + } + case XML_MATHBACKGROUND: + { + if (IsXMLToken(aIter, XML_TRANSPARENT)) + { + SmMlMathbackground aMathbackground + = { SmMlAttributeValueMathbackground::MlTransparent, COL_TRANSPARENT }; + aAttribute.setMlMathbackground(&aMathbackground); + } + else + { + Color aColor + = starmathdatabase::Identify_ColorName_HTML(aIter.toString()).cColor; + SmMlMathbackground aMathbackground + = { SmMlAttributeValueMathbackground::MlRgb, aColor }; + aAttribute.setMlMathbackground(&aMathbackground); + } + break; + } + case XML_MATHCOLOR: + { + if (IsXMLToken(aIter, XML_DEFAULT)) + { + SmMlMathcolor aMathcolor + = { SmMlAttributeValueMathcolor::MlDefault, COL_BLACK }; + aAttribute.setMlMathcolor(&aMathcolor); + } + else + { + Color aColor + = starmathdatabase::Identify_ColorName_HTML(aIter.toString()).cColor; + SmMlMathcolor aMathcolor = { SmMlAttributeValueMathcolor::MlRgb, aColor }; + aAttribute.setMlMathcolor(&aMathcolor); + } + break; + } + case XML_MATHSIZE: + { + SmMlMathsize aMathsize; + aMathsize.m_aLengthValue = handleLengthAttribute(aIter.toString()); + aAttribute.setMlMathsize(&aMathsize); + break; + } + case XML_MATHVARIANT: + { + OUString aVariant = aIter.toString(); + SmMlAttributeValueMathvariant nVariant = SmMlAttributeValueMathvariant::normal; + if (aVariant.compareTo(u"normal")) + nVariant = SmMlAttributeValueMathvariant::normal; + else if (aVariant.compareTo(u"bold")) + nVariant = SmMlAttributeValueMathvariant::bold; + else if (aVariant.compareTo(u"italic")) + nVariant = SmMlAttributeValueMathvariant::italic; + else if (aVariant.compareTo(u"double-struck")) + nVariant = SmMlAttributeValueMathvariant::double_struck; + else if (aVariant.compareTo(u"script")) + nVariant = SmMlAttributeValueMathvariant::script; + else if (aVariant.compareTo(u"fraktur")) + nVariant = SmMlAttributeValueMathvariant::fraktur; + else if (aVariant.compareTo(u"sans-serif")) + nVariant = SmMlAttributeValueMathvariant::sans_serif; + else if (aVariant.compareTo(u"monospace")) + nVariant = SmMlAttributeValueMathvariant::monospace; + else if (aVariant.compareTo(u"bold-italic")) + nVariant = SmMlAttributeValueMathvariant::bold_italic; + else if (aVariant.compareTo(u"bold-fracktur")) + nVariant = SmMlAttributeValueMathvariant::bold_fraktur; + else if (aVariant.compareTo(u"bold-script")) + nVariant = SmMlAttributeValueMathvariant::bold_script; + else if (aVariant.compareTo(u"bold-sans-serif")) + nVariant = SmMlAttributeValueMathvariant::bold_sans_serif; + else if (aVariant.compareTo(u"sans-serif-italic")) + nVariant = SmMlAttributeValueMathvariant::sans_serif_italic; + else if (aVariant.compareTo(u"sans-serif-bold-italic")) + nVariant = SmMlAttributeValueMathvariant::sans_serif_bold_italic; + else if (aVariant.compareTo(u"initial")) + nVariant = SmMlAttributeValueMathvariant::initial; + else if (aVariant.compareTo(u"tailed")) + nVariant = SmMlAttributeValueMathvariant::tailed; + else if (aVariant.compareTo(u"looped")) + nVariant = SmMlAttributeValueMathvariant::looped; + else if (aVariant.compareTo(u"stretched")) + nVariant = SmMlAttributeValueMathvariant::stretched; + else + declareMlError(); + SmMlMathvariant aMathvariant = { nVariant }; + aAttribute.setMlMathvariant(&aMathvariant); + break; + } + case XML_MAXSIZE: + { + SmMlMaxsize aMaxsize; + if (IsXMLToken(aIter, XML_INFINITY)) + { + aMaxsize.m_aMaxsize = SmMlAttributeValueMaxsize::MlInfinity; + aMaxsize.m_aLengthValue + = { SmLengthUnit::MlP, 10000, new OUString(u"10000%"_ustr) }; + } + else + { + aMaxsize.m_aMaxsize = SmMlAttributeValueMaxsize::MlFinite; + aMaxsize.m_aLengthValue = handleLengthAttribute(aIter.toString()); + } + aAttribute.setMlMaxsize(&aMaxsize); + break; + } + case XML_MINSIZE: + { + SmMlMinsize aMinsize; + aMinsize.m_aLengthValue = handleLengthAttribute(aIter.toString()); + aAttribute.setMlMinsize(&aMinsize); + break; + } + case XML_MOVABLELIMITS: + if (IsXMLToken(aIter, XML_TRUE)) + { + aAttribute.setMlAttributeValueType(SmMlAttributeValueType::MlMovablelimits); + SmMlMovablelimits aMovablelimits = { SmMlAttributeValueMovablelimits::MlTrue }; + aAttribute.setMlMovablelimits(&aMovablelimits); + } + else if (IsXMLToken(aIter, XML_FALSE)) + { + aAttribute.setMlAttributeValueType(SmMlAttributeValueType::MlMovablelimits); + SmMlMovablelimits aMovablelimits = { SmMlAttributeValueMovablelimits::MlFalse }; + aAttribute.setMlMovablelimits(&aMovablelimits); + } + else + { + declareMlError(); + } + break; + case XML_RSPACE: + { + SmMlRspace aRspace; + aRspace.m_aLengthValue = handleLengthAttribute(aIter.toString()); + aAttribute.setMlRspace(&aRspace); + break; + } + case XML_SEPARATOR: + if (IsXMLToken(aIter, XML_TRUE)) + { + aAttribute.setMlAttributeValueType(SmMlAttributeValueType::MlSeparator); + SmMlSeparator aSeparator = { SmMlAttributeValueSeparator::MlTrue }; + aAttribute.setMlSeparator(&aSeparator); + } + else if (IsXMLToken(aIter, XML_FALSE)) + { + aAttribute.setMlAttributeValueType(SmMlAttributeValueType::MlSeparator); + SmMlSeparator aSeparator = { SmMlAttributeValueSeparator::MlFalse }; + aAttribute.setMlSeparator(&aSeparator); + } + else + { + declareMlError(); + } + break; + case XML_STRETCHY: + if (IsXMLToken(aIter, XML_TRUE)) + { + aAttribute.setMlAttributeValueType(SmMlAttributeValueType::MlStretchy); + SmMlStretchy aStretchy = { SmMlAttributeValueStretchy::MlTrue }; + aAttribute.setMlStretchy(&aStretchy); + } + else if (IsXMLToken(aIter, XML_FALSE)) + { + aAttribute.setMlAttributeValueType(SmMlAttributeValueType::MlStretchy); + SmMlStretchy aStretchy = { SmMlAttributeValueStretchy::MlFalse }; + aAttribute.setMlStretchy(&aStretchy); + } + else + { + declareMlError(); + } + break; + case XML_SYMMETRIC: + if (IsXMLToken(aIter, XML_TRUE)) + { + aAttribute.setMlAttributeValueType(SmMlAttributeValueType::MlSymmetric); + SmMlSymmetric aSymmetric = { SmMlAttributeValueSymmetric::MlTrue }; + aAttribute.setMlSymmetric(&aSymmetric); + } + else if (IsXMLToken(aIter, XML_FALSE)) + { + aAttribute.setMlAttributeValueType(SmMlAttributeValueType::MlSymmetric); + SmMlSymmetric aSymmetric = { SmMlAttributeValueSymmetric::MlFalse }; + aAttribute.setMlSymmetric(&aSymmetric); + } + else + { + declareMlError(); + } + break; + default: + declareMlError(); + break; + } + if (aAttribute.isNullAttribute()) + declareMlError(); + else + m_pElement->setAttribute(aAttribute); + } +} + +void SmMLImportContext::characters(const OUString& aChars) { m_pElement->setText(aChars); } + +void SmMLImportContext::startFastElement(sal_Int32 nElement, + const Reference<XFastAttributeList>& aAttributeList) +{ + switch (nElement) + { + case XML_ELEMENT(MATH, XML_MATH): + m_pElement = new SmMlElement(SmMlElementType::MlMath); + break; + case XML_ELEMENT(MATH, XML_MI): + m_pElement = new SmMlElement(SmMlElementType::MlMi); + break; + case XML_ELEMENT(MATH, XML_MERROR): + m_pElement = new SmMlElement(SmMlElementType::MlMerror); + break; + case XML_ELEMENT(MATH, XML_MN): + m_pElement = new SmMlElement(SmMlElementType::MlMn); + break; + case XML_ELEMENT(MATH, XML_MO): + m_pElement = new SmMlElement(SmMlElementType::MlMo); + break; + case XML_ELEMENT(MATH, XML_MROW): + m_pElement = new SmMlElement(SmMlElementType::MlMrow); + break; + case XML_ELEMENT(MATH, XML_MTEXT): + m_pElement = new SmMlElement(SmMlElementType::MlMtext); + break; + case XML_ELEMENT(MATH, XML_MSTYLE): + m_pElement = new SmMlElement(SmMlElementType::MlMstyle); + break; + default: + m_pElement = new SmMlElement(SmMlElementType::NMlEmpty); + declareMlError(); + break; + } + SmMlElement* pParent = *m_pParent; + pParent->setSubElement(pParent->getSubElementsCount(), m_pElement); + inheritStyle(); + handleAttributes(aAttributeList); +} + +void SmMLImportContext::endFastElement(sal_Int32) { inheritStyleEnd(); } +} + +// SmMLImport +/*************************************************************************************************/ + +SvXMLImportContext* +SmMLImport::CreateFastContext(sal_Int32 nElement, + const uno::Reference<xml::sax::XFastAttributeList>& /*xAttrList*/) +{ + SvXMLImportContext* pContext = nullptr; + + switch (nElement) + { + case XML_ELEMENT(OFFICE, XML_DOCUMENT): + { + if (m_pElementTree == nullptr) + m_pElementTree = new SmMlElement(SmMlElementType::NMlEmpty); + uno::Reference<document::XDocumentPropertiesSupplier> xDPS(GetModel(), + uno::UNO_QUERY_THROW); + pContext = new SmMLImportContext(*this, &m_pElementTree); + break; + } + case XML_ELEMENT(OFFICE, XML_DOCUMENT_META): + { + uno::Reference<document::XDocumentPropertiesSupplier> xDPS(GetModel(), + uno::UNO_QUERY_THROW); + pContext = new SvXMLMetaDocumentContext(*this, xDPS->getDocumentProperties()); + break; + } + case XML_ELEMENT(OFFICE, XML_DOCUMENT_SETTINGS): + { + uno::Reference<document::XDocumentPropertiesSupplier> xDPS(GetModel(), + uno::UNO_QUERY_THROW); + pContext = new XMLDocumentSettingsContext(*this); + break; + } + default: + declareMlError(); + break; + } + return pContext; +} + +void SmMLImport::endDocument() +{ + uno::Reference<frame::XModel> xModel = GetModel(); + if (!xModel.is()) + { + SAL_WARN("starmath", "Failed to set view settings because missing model"); + SvXMLImport::endDocument(); + return; + } + + SmModel* pModel = comphelper::getFromUnoTunnel<SmModel>(xModel); + if (!pModel) + { + SAL_WARN("starmath", "Failed to set view settings because missing sm model"); + SvXMLImport::endDocument(); + return; + } + + SmDocShell* pDocShell = static_cast<SmDocShell*>(pModel->GetObjectShell()); + if (!pDocShell) + { + SAL_WARN("starmath", "Failed to set view settings because missing sm doc shell"); + SvXMLImport::endDocument(); + return; + } + + // Check if there is element tree + if (m_pElementTree == nullptr) + { + m_bSuccess = true; + SvXMLImport::endDocument(); + return; + } + + // Get element tree and setup + + if (m_pElementTree->getSubElementsCount() == 0) + { + delete m_pElementTree; + m_pElementTree = nullptr; + } + else + { + SmMlElement* pTmpElememt = m_pElementTree->getSubElement(0); + delete m_pElementTree; + m_pElementTree = pTmpElememt; + } + pDocShell->SetMlElementTree(m_pElementTree); + + m_bSuccess = true; + SvXMLImport::endDocument(); +} + +void SmMLImport::SetViewSettings(const Sequence<PropertyValue>& aViewProps) +{ + uno::Reference<frame::XModel> xModel = GetModel(); + if (!xModel.is()) + { + SAL_WARN("starmath", "Failed to set view settings because missing model"); + return; + } + + SmModel* pModel = comphelper::getFromUnoTunnel<SmModel>(xModel); + if (!pModel) + { + SAL_WARN("starmath", "Failed to set view settings because missing sm model"); + return; + } + + SmDocShell* pDocShell = static_cast<SmDocShell*>(pModel->GetObjectShell()); + if (!pDocShell) + { + SAL_WARN("starmath", "Failed to set view settings because missing sm doc shell"); + return; + } + + tools::Rectangle aRect(pDocShell->GetVisArea()); + + tools::Long nTmp = 0; + + for (const PropertyValue& rValue : aViewProps) + { + if (rValue.Name == "ViewAreaTop") + { + rValue.Value >>= nTmp; + aRect.SaturatingSetPosY(nTmp); + } + else if (rValue.Name == "ViewAreaLeft") + { + rValue.Value >>= nTmp; + aRect.SaturatingSetPosX(nTmp); + } + else if (rValue.Name == "ViewAreaWidth") + { + rValue.Value >>= nTmp; + Size aSize(aRect.GetSize()); + aSize.setWidth(nTmp); + aRect.SaturatingSetSize(aSize); + } + else if (rValue.Name == "ViewAreaHeight") + { + rValue.Value >>= nTmp; + Size aSize(aRect.GetSize()); + aSize.setHeight(nTmp); + aRect.SaturatingSetSize(aSize); + } + } + + pDocShell->SetVisArea(aRect); +} + +void SmMLImport::SetConfigurationSettings(const Sequence<PropertyValue>& aConfProps) +{ + uno::Reference<frame::XModel> xModel = GetModel(); + if (!xModel.is()) + { + SAL_WARN("starmath", "Failed to set view settings because missing model"); + return; + } + + uno::Reference<XPropertySet> xProps(xModel, UNO_QUERY); + if (!xProps.is()) + { + SAL_WARN("starmath", "Failed to set view settings because missing model properties"); + return; + } + + Reference<XPropertySetInfo> xInfo(xProps->getPropertySetInfo()); + if (!xInfo.is()) + { + SAL_WARN("starmath", + "Failed to set view settings because missing model properties information"); + return; + } + + static constexpr OUStringLiteral sFormula(u"Formula"); + static constexpr OUStringLiteral sBasicLibraries(u"BasicLibraries"); + static constexpr OUStringLiteral sDialogLibraries(u"DialogLibraries"); + for (const PropertyValue& rValue : aConfProps) + { + if (rValue.Name != sFormula && rValue.Name != sBasicLibraries + && rValue.Name != sDialogLibraries) + { + try + { + if (xInfo->hasPropertyByName(rValue.Name)) + xProps->setPropertyValue(rValue.Name, rValue.Value); + } + catch (const beans::PropertyVetoException&) + { + // dealing with read-only properties here. Nothing to do... + } + catch (const Exception&) + { + SAL_WARN("starmath", "Unexpected issue while loading document properties"); + } + } + } +} + +SmMLImport::SmMLImport(const css::uno::Reference<css::uno::XComponentContext>& rContext, + OUString const& implementationName, SvXMLImportFlags nImportFlags) + : SvXMLImport(rContext, implementationName, nImportFlags) + , m_pElementTree(nullptr) + , m_bSuccess(false) + , m_nSmSyntaxVersion(SM_MOD()->GetConfig()->GetDefaultSmSyntaxVersion()) +{ +} + +/** Handles an error on the mathml structure + */ +void SmMLImport::declareMlError() +{ + m_bSuccess = false; + SAL_WARN("starmath", "MathML error"); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/starmath/source/mathml/iterator.cxx b/starmath/source/mathml/iterator.cxx new file mode 100644 index 0000000000..24de35c183 --- /dev/null +++ b/starmath/source/mathml/iterator.cxx @@ -0,0 +1,77 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <mathml/iterator.hxx> + +/** The purpose of this iterator is to be able to iterate threw an infinite element tree + * infinite -> as much as your memory can hold + * No call-backs that will end up in out of stack + */ + +namespace mathml +{ +static inline void deleteElement(SmMlElement* aSmMlElement, void*) { delete aSmMlElement; } + +static inline void cloneElement(SmMlElement* aSmMlElement, void* aData) +{ + // Prepare data + SmMlElement* aNewSmMlElement = new SmMlElement(*aSmMlElement); + SmMlElement* aCopyTree = *static_cast<SmMlElement**>(aData); + + // Append data + aCopyTree->setSubElement(aCopyTree->getSubElementsCount(), aNewSmMlElement); + + // Prepare for following + // If it has sub elements, then it will be the next + if (aSmMlElement->getSubElementsCount() != 0) + aCopyTree = aNewSmMlElement; + else // Otherwise remounts up to where it should be + { + while (aSmMlElement->getParentElement() != nullptr) + { + // get parent + SmMlElement* pParent = aSmMlElement->getParentElement(); + aCopyTree = aCopyTree->getParentElement(); + // was this the last branch ? + if (aSmMlElement->getSubElementId() + 1 != pParent->getSubElementsCount()) + break; // no -> stop going up + // Prepare for next round + aSmMlElement = pParent; + } + } + + // Closing extras + *static_cast<SmMlElement**>(aData) = aCopyTree; +} + +void SmMlIteratorFree(SmMlElement* pMlElementTree) +{ + if (pMlElementTree == nullptr) + return; + for (size_t i = 0; i < pMlElementTree->getSubElementsCount(); ++i) + { + SmMlIteratorFree(pMlElementTree->getSubElement(i)); + } + deleteElement(pMlElementTree, nullptr); +} + +SmMlElement* SmMlIteratorCopy(SmMlElement* pMlElementTree) +{ + if (pMlElementTree == nullptr) + return nullptr; + SmMlElement* aDummyElement = new SmMlElement(); + SmMlIteratorTopToBottom(pMlElementTree, cloneElement, &aDummyElement); + SmMlElement* aResultElement = aDummyElement->getSubElement(0); + delete aDummyElement; + return aResultElement; +} + +} // end namespace mathml + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/starmath/source/mathml/mathmlMo.cxx b/starmath/source/mathml/mathmlMo.cxx new file mode 100644 index 0000000000..8ee7536e1c --- /dev/null +++ b/starmath/source/mathml/mathmlMo.cxx @@ -0,0 +1,1128 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <mathmlMo.hxx> + +static moOperatorData moOperatorDataDictionaryData[starmathdatabase::MATHML_MO_COUNT] + = { { u"\u2018"_ustr, moOpDF::prefix, 10, 0, 0, moOpDP::fence | moOpDP::nonedp }, + { u"\u2019"_ustr, moOpDF::postfix, 10, 0, 0, moOpDP::fence | moOpDP::nonedp }, + { u"\u201C"_ustr, moOpDF::prefix, 10, 0, 0, moOpDP::fence | moOpDP::nonedp }, + { u"\u201D"_ustr, moOpDF::postfix, 10, 0, 0, moOpDP::fence | moOpDP::nonedp }, + { u"("_ustr, moOpDF::prefix, 20, 0, 0, moOpDP::stretchyfence | moOpDP::symmetric }, + { u")"_ustr, moOpDF::postfix, 20, 0, 0, moOpDP::stretchyfence | moOpDP::symmetric }, + { u"["_ustr, moOpDF::prefix, 20, 0, 0, moOpDP::stretchyfence | moOpDP::symmetric }, + { u"]"_ustr, moOpDF::postfix, 20, 0, 0, moOpDP::stretchyfence | moOpDP::symmetric }, + { u"{"_ustr, moOpDF::prefix, 20, 0, 0, moOpDP::stretchyfence | moOpDP::symmetric }, + { u"|"_ustr, moOpDF::prepostfix, 20, 0, 0, moOpDP::stretchyfence | moOpDP::symmetric }, + { u"||"_ustr, moOpDF::prepostfix, 20, 0, 0, moOpDP::stretchyfence | moOpDP::symmetric }, + { u"|||"_ustr, moOpDF::prepostfix, 20, 0, 0, moOpDP::stretchyfence | moOpDP::symmetric }, + { u"}"_ustr, moOpDF::postfix, 20, 0, 0, moOpDP::stretchyfence | moOpDP::symmetric }, + { u"\u2016"_ustr, moOpDF::prepostfix, 20, 0, 0, moOpDP::stretchyfence }, + { u"\u2308"_ustr, moOpDF::prefix, 20, 0, 0, moOpDP::stretchyfence | moOpDP::symmetric }, + { u"\u2309"_ustr, moOpDF::postfix, 20, 0, 0, moOpDP::stretchyfence | moOpDP::symmetric }, + { u"\u230A"_ustr, moOpDF::prefix, 20, 0, 0, moOpDP::stretchyfence | moOpDP::symmetric }, + { u"\u230B"_ustr, moOpDF::postfix, 20, 0, 0, moOpDP::stretchyfence | moOpDP::symmetric }, + { u"\u2329"_ustr, moOpDF::prefix, 20, 0, 0, moOpDP::stretchyfence | moOpDP::symmetric }, + { u"\u232A"_ustr, moOpDF::postfix, 20, 0, 0, moOpDP::stretchyfence | moOpDP::symmetric }, + { u"\u2772"_ustr, moOpDF::prefix, 20, 0, 0, moOpDP::stretchyfence | moOpDP::symmetric }, + { u"\u2773"_ustr, moOpDF::postfix, 20, 0, 0, moOpDP::stretchyfence | moOpDP::symmetric }, + { u"\u27E6"_ustr, moOpDF::prefix, 20, 0, 0, moOpDP::stretchyfence | moOpDP::symmetric }, + { u"\u27E7"_ustr, moOpDF::postfix, 20, 0, 0, moOpDP::stretchyfence | moOpDP::symmetric }, + { u"\u27E8"_ustr, moOpDF::prefix, 20, 0, 0, moOpDP::stretchyfence | moOpDP::symmetric }, + { u"\u27E9"_ustr, moOpDF::postfix, 20, 0, 0, moOpDP::stretchyfence | moOpDP::symmetric }, + { u"\u27EA"_ustr, moOpDF::prefix, 20, 0, 0, moOpDP::stretchyfence | moOpDP::symmetric }, + { u"\u27EB"_ustr, moOpDF::postfix, 20, 0, 0, moOpDP::stretchyfence | moOpDP::symmetric }, + { u"\u27EC"_ustr, moOpDF::prefix, 20, 0, 0, moOpDP::stretchyfence | moOpDP::symmetric }, + { u"\u27ED"_ustr, moOpDF::postfix, 20, 0, 0, moOpDP::stretchyfence | moOpDP::symmetric }, + { u"\u27EE"_ustr, moOpDF::prefix, 20, 0, 0, moOpDP::stretchyfence | moOpDP::symmetric }, + { u"\u27EF"_ustr, moOpDF::postfix, 20, 0, 0, moOpDP::stretchyfence | moOpDP::symmetric }, + { u"\u2980"_ustr, moOpDF::prepostfix, 20, 0, 0, moOpDP::stretchyfence }, + { u"\u2983"_ustr, moOpDF::prefix, 20, 0, 0, moOpDP::stretchyfence | moOpDP::symmetric }, + { u"\u2984"_ustr, moOpDF::postfix, 20, 0, 0, moOpDP::stretchyfence | moOpDP::symmetric }, + { u"\u2985"_ustr, moOpDF::prefix, 20, 0, 0, moOpDP::stretchyfence | moOpDP::symmetric }, + { u"\u2986"_ustr, moOpDF::postfix, 20, 0, 0, moOpDP::stretchyfence | moOpDP::symmetric }, + { u"\u2987"_ustr, moOpDF::prefix, 20, 0, 0, moOpDP::stretchyfence | moOpDP::symmetric }, + { u"\u2988"_ustr, moOpDF::postfix, 20, 0, 0, moOpDP::stretchyfence | moOpDP::symmetric }, + { u"\u2989"_ustr, moOpDF::prefix, 20, 0, 0, moOpDP::stretchyfence | moOpDP::symmetric }, + { u"\u298A"_ustr, moOpDF::postfix, 20, 0, 0, moOpDP::stretchyfence | moOpDP::symmetric }, + { u"\u298B"_ustr, moOpDF::prefix, 20, 0, 0, moOpDP::stretchyfence | moOpDP::symmetric }, + { u"\u298C"_ustr, moOpDF::postfix, 20, 0, 0, moOpDP::stretchyfence | moOpDP::symmetric }, + { u"\u298D"_ustr, moOpDF::prefix, 20, 0, 0, moOpDP::stretchyfence | moOpDP::symmetric }, + { u"\u298E"_ustr, moOpDF::postfix, 20, 0, 0, moOpDP::stretchyfence | moOpDP::symmetric }, + { u"\u298F"_ustr, moOpDF::prefix, 20, 0, 0, moOpDP::stretchyfence | moOpDP::symmetric }, + { u"\u2990"_ustr, moOpDF::postfix, 20, 0, 0, moOpDP::stretchyfence | moOpDP::symmetric }, + { u"\u2991"_ustr, moOpDF::prefix, 20, 0, 0, moOpDP::stretchyfence | moOpDP::symmetric }, + { u"\u2992"_ustr, moOpDF::postfix, 20, 0, 0, moOpDP::stretchyfence | moOpDP::symmetric }, + { u"\u2993"_ustr, moOpDF::prefix, 20, 0, 0, moOpDP::stretchyfence | moOpDP::symmetric }, + { u"\u2994"_ustr, moOpDF::postfix, 20, 0, 0, moOpDP::stretchyfence | moOpDP::symmetric }, + { u"\u2995"_ustr, moOpDF::prefix, 20, 0, 0, moOpDP::stretchyfence | moOpDP::symmetric }, + { u"\u2996"_ustr, moOpDF::postfix, 20, 0, 0, moOpDP::stretchyfence | moOpDP::symmetric }, + { u"\u2997"_ustr, moOpDF::prefix, 20, 0, 0, moOpDP::stretchyfence | moOpDP::symmetric }, + { u"\u2998"_ustr, moOpDF::postfix, 20, 0, 0, moOpDP::stretchyfence | moOpDP::symmetric }, + { u"\u29FC"_ustr, moOpDF::prefix, 20, 0, 0, moOpDP::stretchyfence | moOpDP::symmetric }, + { u"\u29FD"_ustr, moOpDF::postfix, 20, 0, 0, moOpDP::stretchyfence | moOpDP::symmetric }, + { u";"_ustr, moOpDF::infix, 30, 0, 3, moOpDP::separator | moOpDP::linebreakstyleAfter }, + { u","_ustr, moOpDF::infix, 40, 0, 3, moOpDP::separator | moOpDP::linebreakstyleAfter }, + { u"\u2063"_ustr, moOpDF::infix, 40, 0, 0, + moOpDP::separator | moOpDP::linebreakstyleAfter }, + { u"\u2234"_ustr, moOpDF::infix, 70, 5, 5, moOpDP::nonedp }, + { u"\u2235"_ustr, moOpDF::infix, 70, 5, 5, moOpDP::nonedp }, + { u"->"_ustr, moOpDF::infix, 90, 5, 5, moOpDP::nonedp }, + { u".."_ustr, moOpDF::postfix, 100, 0, 0, moOpDP::nonedp }, + { u"..."_ustr, moOpDF::postfix, 100, 0, 0, moOpDP::nonedp }, + { u":"_ustr, moOpDF::infix, 100, 1, 2, moOpDP::nonedp }, + { u"\u03F6"_ustr, moOpDF::infix, 110, 5, 5, moOpDP::nonedp }, + { u"\u2026"_ustr, moOpDF::infix, 150, 0, 0, moOpDP::nonedp }, + { u"\u22EE"_ustr, moOpDF::infix, 150, 5, 5, moOpDP::nonedp }, + { u"\u22EF"_ustr, moOpDF::infix, 150, 0, 0, moOpDP::nonedp }, + { u"\u22F1"_ustr, moOpDF::infix, 150, 5, 5, moOpDP::nonedp }, + { u"\u220B"_ustr, moOpDF::infix, 160, 5, 5, moOpDP::nonedp }, + { u"\u22A2"_ustr, moOpDF::infix, 170, 5, 5, moOpDP::nonedp }, + { u"\u22A3"_ustr, moOpDF::infix, 170, 5, 5, moOpDP::nonedp }, + { u"\u22A4"_ustr, moOpDF::infix, 170, 5, 5, moOpDP::nonedp }, + { u"\u22A8"_ustr, moOpDF::infix, 170, 5, 5, moOpDP::nonedp }, + { u"\u22A9"_ustr, moOpDF::infix, 170, 5, 5, moOpDP::nonedp }, + { u"\u22AC"_ustr, moOpDF::infix, 170, 5, 5, moOpDP::nonedp }, + { u"\u22AD"_ustr, moOpDF::infix, 170, 5, 5, moOpDP::nonedp }, + { u"\u22AE"_ustr, moOpDF::infix, 170, 5, 5, moOpDP::nonedp }, + { u"\u22AF"_ustr, moOpDF::infix, 170, 5, 5, moOpDP::nonedp }, + { u"\u2228"_ustr, moOpDF::infix, 190, 4, 4, moOpDP::nonedp }, + { u"&&"_ustr, moOpDF::infix, 200, 4, 4, moOpDP::nonedp }, + { u"\u2227"_ustr, moOpDF::infix, 200, 4, 4, moOpDP::nonedp }, + { u"\u2200"_ustr, moOpDF::prefix, 230, 2, 1, moOpDP::nonedp }, + { u"\u2203"_ustr, moOpDF::prefix, 230, 2, 1, moOpDP::nonedp }, + { u"\u2204"_ustr, moOpDF::prefix, 230, 2, 1, moOpDP::nonedp }, + { u"\u2201"_ustr, moOpDF::infix, 240, 1, 2, moOpDP::nonedp }, + { u"\u2208"_ustr, moOpDF::infix, 240, 5, 5, moOpDP::nonedp }, + { u"\u2209"_ustr, moOpDF::infix, 240, 5, 5, moOpDP::nonedp }, + { u"\u220C"_ustr, moOpDF::infix, 240, 5, 5, moOpDP::nonedp }, + { u"\u2282"_ustr, moOpDF::infix, 240, 5, 5, moOpDP::nonedp }, + { u"\u2282\u20D2"_ustr, moOpDF::infix, 240, 5, 5, moOpDP::nonedp }, + { u"\u2283"_ustr, moOpDF::infix, 240, 5, 5, moOpDP::nonedp }, + { u"\u2283\u20D2"_ustr, moOpDF::infix, 240, 5, 5, moOpDP::nonedp }, + { u"\u2284"_ustr, moOpDF::infix, 240, 5, 5, moOpDP::nonedp }, + { u"\u2285"_ustr, moOpDF::infix, 240, 5, 5, moOpDP::nonedp }, + { u"\u2286"_ustr, moOpDF::infix, 240, 5, 5, moOpDP::nonedp }, + { u"\u2287"_ustr, moOpDF::infix, 240, 5, 5, moOpDP::nonedp }, + { u"\u2288"_ustr, moOpDF::infix, 240, 5, 5, moOpDP::nonedp }, + { u"\u2289"_ustr, moOpDF::infix, 240, 5, 5, moOpDP::nonedp }, + { u"\u228A"_ustr, moOpDF::infix, 240, 5, 5, moOpDP::nonedp }, + { u"\u228B"_ustr, moOpDF::infix, 240, 5, 5, moOpDP::nonedp }, + { u"<="_ustr, moOpDF::infix, 241, 5, 5, moOpDP::nonedp }, + { u"\u2264"_ustr, moOpDF::infix, 241, 5, 5, moOpDP::nonedp }, + { u"\u2265"_ustr, moOpDF::infix, 242, 5, 5, moOpDP::nonedp }, + { u">"_ustr, moOpDF::infix, 243, 5, 5, moOpDP::nonedp }, + { u">="_ustr, moOpDF::infix, 243, 5, 5, moOpDP::nonedp }, + { u"\u226F"_ustr, moOpDF::infix, 244, 5, 5, moOpDP::nonedp }, + { u"<"_ustr, moOpDF::infix, 245, 5, 5, moOpDP::nonedp }, + { u"\u226E"_ustr, moOpDF::infix, 246, 5, 5, moOpDP::nonedp }, + { u"\u2248"_ustr, moOpDF::infix, 247, 5, 5, moOpDP::nonedp }, + { u"\u223C"_ustr, moOpDF::infix, 250, 5, 5, moOpDP::nonedp }, + { u"\u2249"_ustr, moOpDF::infix, 250, 5, 5, moOpDP::nonedp }, + { u"\u2262"_ustr, moOpDF::infix, 252, 5, 5, moOpDP::nonedp }, + { u"\u2260"_ustr, moOpDF::infix, 255, 5, 5, moOpDP::nonedp }, + { u"!="_ustr, moOpDF::infix, 260, 4, 4, moOpDP::nonedp }, + { u"*="_ustr, moOpDF::infix, 260, 4, 4, moOpDP::nonedp }, + { u"+="_ustr, moOpDF::infix, 260, 4, 4, moOpDP::nonedp }, + { u"-="_ustr, moOpDF::infix, 260, 4, 4, moOpDP::nonedp }, + { u"/="_ustr, moOpDF::infix, 260, 4, 4, moOpDP::nonedp }, + { u":="_ustr, moOpDF::infix, 260, 4, 4, moOpDP::nonedp }, + { u"="_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"=="_ustr, moOpDF::infix, 260, 4, 4, moOpDP::nonedp }, + { u"\u221D"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u2224"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u2225"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u2226"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u2241"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u2243"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u2244"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u2245"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u2246"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u2247"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u224D"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u2254"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u2257"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u2259"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u225A"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u225B"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u225C"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u225F"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u2261"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u2268"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u2269"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u226A"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u226A\u0338"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u226B"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u226B\u0338"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u226D"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u2270"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u2271"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u227A"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u227B"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u227C"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u227D"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u2280"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u2281"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u22A5"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u22B4"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u22B5"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u22C9"_ustr, moOpDF::infix, 260, 4, 4, moOpDP::nonedp }, + { u"\u22CA"_ustr, moOpDF::infix, 260, 4, 4, moOpDP::nonedp }, + { u"\u22CB"_ustr, moOpDF::infix, 260, 4, 4, moOpDP::nonedp }, + { u"\u22CC"_ustr, moOpDF::infix, 260, 4, 4, moOpDP::nonedp }, + { u"\u22D4"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u22D6"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u22D7"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u22D8"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u22D9"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u22EA"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u22EB"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u22EC"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u22ED"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u25A0"_ustr, moOpDF::infix, 260, 3, 3, moOpDP::nonedp }, + { u"\u25A1"_ustr, moOpDF::infix, 260, 3, 3, moOpDP::nonedp }, + { u"\u25AA"_ustr, moOpDF::infix, 260, 3, 3, moOpDP::nonedp }, + { u"\u25AB"_ustr, moOpDF::infix, 260, 3, 3, moOpDP::nonedp }, + { u"\u25AD"_ustr, moOpDF::infix, 260, 3, 3, moOpDP::nonedp }, + { u"\u25AE"_ustr, moOpDF::infix, 260, 3, 3, moOpDP::nonedp }, + { u"\u25AF"_ustr, moOpDF::infix, 260, 3, 3, moOpDP::nonedp }, + { u"\u25B0"_ustr, moOpDF::infix, 260, 3, 3, moOpDP::nonedp }, + { u"\u25B1"_ustr, moOpDF::infix, 260, 3, 3, moOpDP::nonedp }, + { u"\u25B3"_ustr, moOpDF::infix, 260, 4, 4, moOpDP::nonedp }, + { u"\u25B4"_ustr, moOpDF::infix, 260, 4, 4, moOpDP::nonedp }, + { u"\u25B5"_ustr, moOpDF::infix, 260, 4, 4, moOpDP::nonedp }, + { u"\u25B6"_ustr, moOpDF::infix, 260, 4, 4, moOpDP::nonedp }, + { u"\u25B7"_ustr, moOpDF::infix, 260, 4, 4, moOpDP::nonedp }, + { u"\u25B8"_ustr, moOpDF::infix, 260, 4, 4, moOpDP::nonedp }, + { u"\u25B9"_ustr, moOpDF::infix, 260, 4, 4, moOpDP::nonedp }, + { u"\u25BC"_ustr, moOpDF::infix, 260, 4, 4, moOpDP::nonedp }, + { u"\u25BD"_ustr, moOpDF::infix, 260, 4, 4, moOpDP::nonedp }, + { u"\u25BE"_ustr, moOpDF::infix, 260, 4, 4, moOpDP::nonedp }, + { u"\u25BF"_ustr, moOpDF::infix, 260, 4, 4, moOpDP::nonedp }, + { u"\u25C0"_ustr, moOpDF::infix, 260, 4, 4, moOpDP::nonedp }, + { u"\u25C1"_ustr, moOpDF::infix, 260, 4, 4, moOpDP::nonedp }, + { u"\u25C2"_ustr, moOpDF::infix, 260, 4, 4, moOpDP::nonedp }, + { u"\u25C3"_ustr, moOpDF::infix, 260, 4, 4, moOpDP::nonedp }, + { u"\u25C4"_ustr, moOpDF::infix, 260, 4, 4, moOpDP::nonedp }, + { u"\u25C5"_ustr, moOpDF::infix, 260, 4, 4, moOpDP::nonedp }, + { u"\u25C6"_ustr, moOpDF::infix, 260, 4, 4, moOpDP::nonedp }, + { u"\u25C7"_ustr, moOpDF::infix, 260, 4, 4, moOpDP::nonedp }, + { u"\u25C8"_ustr, moOpDF::infix, 260, 4, 4, moOpDP::nonedp }, + { u"\u25C9"_ustr, moOpDF::infix, 260, 4, 4, moOpDP::nonedp }, + { u"\u25CC"_ustr, moOpDF::infix, 260, 4, 4, moOpDP::nonedp }, + { u"\u25CD"_ustr, moOpDF::infix, 260, 4, 4, moOpDP::nonedp }, + { u"\u25CE"_ustr, moOpDF::infix, 260, 4, 4, moOpDP::nonedp }, + { u"\u25CF"_ustr, moOpDF::infix, 260, 4, 4, moOpDP::nonedp }, + { u"\u25D6"_ustr, moOpDF::infix, 260, 4, 4, moOpDP::nonedp }, + { u"\u25D7"_ustr, moOpDF::infix, 260, 4, 4, moOpDP::nonedp }, + { u"\u25E6"_ustr, moOpDF::infix, 260, 4, 4, moOpDP::nonedp }, + { u"\u29C0"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u29C1"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u29E3"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u29E4"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u29E5"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u29E6"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u29F3"_ustr, moOpDF::infix, 260, 3, 3, moOpDP::nonedp }, + { u"\u2A87"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u2A88"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u2AAF"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u2AAF\u0338"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u2AB0"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u2AB0\u0338"_ustr, moOpDF::infix, 260, 5, 5, moOpDP::nonedp }, + { u"\u2044"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::stretchy }, + { u"\u2206"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u220A"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u220D"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u220E"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u2215"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::stretchy }, + { u"\u2217"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2218"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2219"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u221F"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2223"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2236"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2237"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2238"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2239"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u223A"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u223B"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u223D"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u223D\u0331"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u223E"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u223F"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u2242"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2242\u0338"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u224A"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u224B"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u224C"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u224E"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u224E\u0338"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u224F"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u224F\u0338"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2250"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2251"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2252"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2253"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2255"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2256"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2258"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u225D"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u225E"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2263"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2266"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2266\u0338"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2267"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u226C"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2272"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2273"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2274"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2275"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2276"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2277"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2278"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2279"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u227E"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u227F"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u227F\u0338"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u228C"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u228D"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u228E"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u228F"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u228F\u0338"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2290"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2290\u0338"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2291"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2292"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2293"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2294"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u229A"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u229B"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u229C"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u229D"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u22A6"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u22A7"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u22AA"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u22AB"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u22B0"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u22B1"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u22B2"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u22B3"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u22B6"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u22B7"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u22B9"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u22BA"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u22BB"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u22BC"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u22BD"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u22BE"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u22BF"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u22C4"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u22C6"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u22C7"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u22C8"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u22CD"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u22CE"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u22CF"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u22D0"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u22D1"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u22D2"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u22D3"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u22D5"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u22DA"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u22DB"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u22DC"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u22DD"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u22DE"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u22DF"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u22E0"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u22E1"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u22E2"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u22E3"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u22E4"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u22E5"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u22E6"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u22E7"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u22E8"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u22E9"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u22F0"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u22F2"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u22F3"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u22F4"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u22F5"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u22F6"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u22F7"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u22F8"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u22F9"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u22FA"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u22FB"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u22FC"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u22FD"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u22FE"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u22FF"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u25B2"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2758"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2981"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u2982"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u29A0"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u29A1"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u29A2"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u29A3"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u29A4"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u29A5"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u29A6"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u29A7"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u29A8"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u29A9"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u29AA"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u29AB"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u29AC"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u29AD"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u29AE"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u29AF"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u29B0"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u29B1"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u29B2"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u29B3"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u29B4"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u29B5"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u29B6"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u29B7"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u29B8"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u29B9"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u29BA"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u29BB"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u29BC"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u29BD"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u29BE"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u29BF"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u29C2"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u29C3"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u29C4"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u29C5"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u29C6"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u29C7"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u29C8"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u29C9"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u29CA"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u29CB"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u29CC"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u29CD"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u29CE"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u29CF"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u29CF\u0338"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u29D0"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u29D0\u0338"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u29D1"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u29D2"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u29D3"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u29D4"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u29D5"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u29D6"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u29D7"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u29D8"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u29D9"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u29DB"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u29DC"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u29DD"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u29DE"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u29E0"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u29E1"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u29E2"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u29E7"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u29E8"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u29E9"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u29EA"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u29EB"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u29EC"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u29ED"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u29EE"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u29F0"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u29F1"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u29F2"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u29F5"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u29F6"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u29F7"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u29F8"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u29F9"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u29FA"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u29FB"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u29FE"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u29FF"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A1D"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u2A1E"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u2A1F"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u2A20"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u2A21"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"\u2A22"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A23"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A24"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A25"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A26"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A27"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A28"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A29"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A2A"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A2B"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A2C"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A2D"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A2E"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A30"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A31"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A32"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A33"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A34"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A35"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A36"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A37"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A38"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A39"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A3A"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A3B"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A3C"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A3D"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A3E"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A40"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A41"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A42"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A43"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A44"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A45"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A46"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A47"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A48"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A49"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A4A"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A4B"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A4C"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A4D"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A4E"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A4F"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A50"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A51"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A52"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A53"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A54"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A55"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A56"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A57"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A58"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A59"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A5A"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A5B"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A5C"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A5D"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A5E"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A5F"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A60"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A61"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A62"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A63"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A64"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A65"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A66"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A67"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A68"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A69"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A6A"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A6B"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A6C"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A6D"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A6E"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A6F"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A70"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A71"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A72"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2A73"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A74"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A75"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A76"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A77"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A78"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A79"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A7A"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A7B"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A7C"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A7D"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A7D\u0338"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A7E"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A7E\u0338"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A7F"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A80"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A81"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A82"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A83"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A84"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A85"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A86"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A89"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A8A"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A8B"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A8C"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A8D"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A8E"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A8F"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A90"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A91"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A92"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A93"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A94"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A95"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A96"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A97"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A98"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A99"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A9A"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A9B"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A9C"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A9D"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A9E"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2A9F"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AA0"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AA1"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AA1\u0338"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AA2"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AA2\u0338"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AA3"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AA4"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AA5"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AA6"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AA7"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AA8"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AA9"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AAA"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AAB"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AAC"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AAD"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AAE"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AB1"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AB2"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AB3"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AB4"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AB5"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AB6"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AB7"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AB8"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AB9"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2ABA"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2ABB"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2ABC"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2ABD"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2ABE"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2ABF"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AC0"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AC1"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AC2"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AC3"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AC4"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AC5"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AC6"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AC7"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AC8"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AC9"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2ACA"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2ACB"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2ACC"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2ACD"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2ACE"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2ACF"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AD0"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AD1"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AD2"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AD3"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AD4"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AD5"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AD6"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AD7"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AD8"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AD9"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2ADA"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2ADB"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2ADD"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2ADD\u0338"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2ADE"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2ADF"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AE0"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AE1"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AE2"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AE3"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AE4"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AE5"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AE6"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AE7"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AE8"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AE9"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AEA"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AEB"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AEC"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AED"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AEE"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AEF"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AF0"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AF1"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AF2"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AF3"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AF4"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2AF5"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2AF6"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2AF7"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AF8"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AF9"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AFA"_ustr, moOpDF::infix, 265, 5, 5, moOpDP::nonedp }, + { u"\u2AFB"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2AFD"_ustr, moOpDF::infix, 265, 4, 4, moOpDP::nonedp }, + { u"\u2AFE"_ustr, moOpDF::infix, 265, 3, 3, moOpDP::nonedp }, + { u"|"_ustr, moOpDF::infix, 270, 2, 2, moOpDP::stretchyfence | moOpDP::symmetric }, + { u"||"_ustr, moOpDF::infix, 270, 2, 2, moOpDP::stretchyfence | moOpDP::symmetric }, + { u"|||"_ustr, moOpDF::infix, 270, 2, 2, moOpDP::stretchyfence | moOpDP::symmetric }, + { u"\u2190"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u2191"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u2192"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u2193"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u2194"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u2195"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u2196"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u2197"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u2198"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u2199"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u219A"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u219B"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u219C"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u219D"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u219E"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u219F"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u21A0"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u21A1"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u21A2"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u21A3"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u21A4"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u21A5"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u21A6"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u21A7"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u21A8"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u21A9"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u21AA"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u21AB"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u21AC"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u21AD"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u21AE"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u21AF"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u21B0"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u21B1"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u21B2"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u21B3"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u21B4"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u21B5"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u21B6"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u21B7"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u21B8"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::nonedp }, + { u"\u21B9"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u21BA"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::nonedp }, + { u"\u21BB"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::nonedp }, + { u"\u21BC"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u21BD"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u21BE"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u21BF"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u21C0"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u21C1"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u21C2"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u21C3"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u21C4"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u21C5"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u21C6"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u21C7"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u21C8"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u21C9"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u21CA"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u21CB"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u21CC"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u21CD"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u21CE"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u21CF"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u21D0"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u21D1"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u21D2"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u21D3"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u21D4"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u21D5"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u21D6"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u21D7"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u21D8"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u21D9"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u21DA"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u21DB"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u21DC"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u21DD"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u21DE"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::nonedp }, + { u"\u21DF"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::nonedp }, + { u"\u21E0"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u21E1"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u21E2"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u21E3"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u21E4"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u21E5"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u21E6"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u21E7"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u21E8"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u21E9"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u21EA"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u21EB"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u21EC"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u21ED"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u21EE"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u21EF"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u21F0"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u21F1"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::nonedp }, + { u"\u21F2"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::nonedp }, + { u"\u21F3"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u21F4"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u21F5"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u21F6"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u21F7"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u21F8"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u21F9"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u21FA"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u21FB"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u21FC"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u21FD"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u21FE"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u21FF"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u22B8"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::nonedp }, + { u"\u27F0"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u27F1"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u27F5"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u27F6"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u27F7"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u27F8"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u27F9"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u27FA"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u27FB"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u27FC"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u27FD"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u27FE"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u27FF"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u2900"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u2901"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u2902"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u2903"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u2904"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u2905"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u2906"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u2907"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u2908"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::nonedp }, + { u"\u2909"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::nonedp }, + { u"\u290A"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u290B"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u290C"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u290D"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u290E"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u290F"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u2910"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u2911"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u2912"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u2913"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u2914"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u2915"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u2916"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u2917"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u2918"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u2919"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u291A"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u291B"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u291C"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u291D"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u291E"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u291F"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u2920"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u2921"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u2922"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u2923"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::nonedp }, + { u"\u2924"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::nonedp }, + { u"\u2925"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::nonedp }, + { u"\u2926"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::nonedp }, + { u"\u2927"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::nonedp }, + { u"\u2928"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::nonedp }, + { u"\u2929"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::nonedp }, + { u"\u292A"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::nonedp }, + { u"\u292B"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::nonedp }, + { u"\u292C"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::nonedp }, + { u"\u292D"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::nonedp }, + { u"\u292E"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::nonedp }, + { u"\u292F"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::nonedp }, + { u"\u2930"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::nonedp }, + { u"\u2931"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::nonedp }, + { u"\u2932"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::nonedp }, + { u"\u2933"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u2934"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::nonedp }, + { u"\u2935"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::nonedp }, + { u"\u2936"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::nonedp }, + { u"\u2937"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::nonedp }, + { u"\u2938"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::nonedp }, + { u"\u2939"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::nonedp }, + { u"\u293A"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u293B"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u293C"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u293D"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u293E"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::nonedp }, + { u"\u293F"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::nonedp }, + { u"\u2940"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::nonedp }, + { u"\u2941"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::nonedp }, + { u"\u2942"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u2943"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u2944"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u2945"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u2946"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u2947"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u2948"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u2949"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::nonedp }, + { u"\u294A"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u294B"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u294C"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::nonedp }, + { u"\u294D"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::nonedp }, + { u"\u294E"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u294F"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u2950"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u2951"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u2952"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u2953"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u2954"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u2955"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u2956"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u2957"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u2958"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u2959"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u295A"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u295B"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u295C"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u295D"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u295E"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u295F"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy | moOpDP::accent }, + { u"\u2960"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u2961"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u2962"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u2963"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::nonedp }, + { u"\u2964"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u2965"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::nonedp }, + { u"\u2966"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u2967"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u2968"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u2969"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u296A"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u296B"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u296C"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u296D"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u296E"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u296F"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u2970"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u2971"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u2972"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u2973"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u2974"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u2975"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u2976"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u2977"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u2978"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u2979"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u297A"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u297B"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u297C"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u297D"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::accent }, + { u"\u297E"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::nonedp }, + { u"\u297F"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::nonedp }, + { u"\u2999"_ustr, moOpDF::infix, 270, 3, 3, moOpDP::nonedp }, + { u"\u299A"_ustr, moOpDF::infix, 270, 3, 3, moOpDP::nonedp }, + { u"\u299B"_ustr, moOpDF::infix, 270, 3, 3, moOpDP::nonedp }, + { u"\u299C"_ustr, moOpDF::infix, 270, 3, 3, moOpDP::nonedp }, + { u"\u299D"_ustr, moOpDF::infix, 270, 3, 3, moOpDP::nonedp }, + { u"\u299E"_ustr, moOpDF::infix, 270, 3, 3, moOpDP::nonedp }, + { u"\u299F"_ustr, moOpDF::infix, 270, 3, 3, moOpDP::nonedp }, + { u"\u29DF"_ustr, moOpDF::infix, 270, 3, 3, moOpDP::nonedp }, + { u"\u29EF"_ustr, moOpDF::infix, 270, 3, 3, moOpDP::nonedp }, + { u"\u29F4"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::nonedp }, + { u"\u2B45"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"\u2B46"_ustr, moOpDF::infix, 270, 5, 5, moOpDP::stretchy }, + { u"+"_ustr, moOpDF::infix, 275, 4, 4, moOpDP::nonedp }, + { u"+"_ustr, moOpDF::prefix, 275, 0, 1, moOpDP::nonedp }, + { u"-"_ustr, moOpDF::infix, 275, 4, 4, moOpDP::nonedp }, + { u"-"_ustr, moOpDF::prefix, 275, 0, 1, moOpDP::nonedp }, + { u"\u00B1"_ustr, moOpDF::infix, 275, 4, 4, moOpDP::nonedp }, + { u"\u00B1"_ustr, moOpDF::prefix, 275, 0, 1, moOpDP::nonedp }, + { u"\u2212"_ustr, moOpDF::infix, 275, 4, 4, moOpDP::nonedp }, + { u"\u2212"_ustr, moOpDF::prefix, 275, 0, 1, moOpDP::nonedp }, + { u"\u2213"_ustr, moOpDF::infix, 275, 4, 4, moOpDP::nonedp }, + { u"\u2213"_ustr, moOpDF::prefix, 275, 0, 1, moOpDP::nonedp }, + { u"\u2214"_ustr, moOpDF::infix, 275, 4, 4, moOpDP::nonedp }, + { u"\u229E"_ustr, moOpDF::infix, 275, 4, 4, moOpDP::nonedp }, + { u"\u229F"_ustr, moOpDF::infix, 275, 4, 4, moOpDP::nonedp }, + { u"\u2211"_ustr, moOpDF::prefix, 290, 1, 2, moOpDP::movablelargeop | moOpDP::symmetric }, + { u"\u2A0A"_ustr, moOpDF::prefix, 290, 1, 2, moOpDP::movablelargeop | moOpDP::symmetric }, + { u"\u2A0B"_ustr, moOpDF::prefix, 290, 1, 2, moOpDP::largeop | moOpDP::symmetric }, + { u"\u222C"_ustr, moOpDF::prefix, 300, 0, 1, moOpDP::largeop | moOpDP::symmetric }, + { u"\u222D"_ustr, moOpDF::prefix, 300, 0, 1, moOpDP::largeop | moOpDP::symmetric }, + { u"\u2295"_ustr, moOpDF::infix, 300, 4, 4, moOpDP::nonedp }, + { u"\u2296"_ustr, moOpDF::infix, 300, 4, 4, moOpDP::nonedp }, + { u"\u2298"_ustr, moOpDF::infix, 300, 4, 4, moOpDP::nonedp }, + { u"\u2A01"_ustr, moOpDF::prefix, 300, 1, 2, moOpDP::movablelargeop | moOpDP::symmetric }, + { u"\u222B"_ustr, moOpDF::prefix, 310, 0, 1, moOpDP::largeop | moOpDP::symmetric }, + { u"\u222E"_ustr, moOpDF::prefix, 310, 0, 1, moOpDP::largeop | moOpDP::symmetric }, + { u"\u222F"_ustr, moOpDF::prefix, 310, 0, 1, moOpDP::largeop | moOpDP::symmetric }, + { u"\u2230"_ustr, moOpDF::prefix, 310, 0, 1, moOpDP::largeop | moOpDP::symmetric }, + { u"\u2231"_ustr, moOpDF::prefix, 310, 0, 1, moOpDP::largeop | moOpDP::symmetric }, + { u"\u2232"_ustr, moOpDF::prefix, 310, 0, 1, moOpDP::largeop | moOpDP::symmetric }, + { u"\u2233"_ustr, moOpDF::prefix, 310, 0, 1, moOpDP::largeop | moOpDP::symmetric }, + { u"\u2A0C"_ustr, moOpDF::prefix, 310, 0, 1, moOpDP::largeop | moOpDP::symmetric }, + { u"\u2A0D"_ustr, moOpDF::prefix, 310, 1, 2, moOpDP::largeop | moOpDP::symmetric }, + { u"\u2A0E"_ustr, moOpDF::prefix, 310, 1, 2, moOpDP::largeop | moOpDP::symmetric }, + { u"\u2A0F"_ustr, moOpDF::prefix, 310, 1, 2, moOpDP::largeop | moOpDP::symmetric }, + { u"\u2A10"_ustr, moOpDF::prefix, 310, 1, 2, moOpDP::movablelargeop | moOpDP::symmetric }, + { u"\u2A11"_ustr, moOpDF::prefix, 310, 1, 2, moOpDP::movablelargeop | moOpDP::symmetric }, + { u"\u2A12"_ustr, moOpDF::prefix, 310, 1, 2, moOpDP::movablelargeop | moOpDP::symmetric }, + { u"\u2A13"_ustr, moOpDF::prefix, 310, 1, 2, moOpDP::movablelargeop | moOpDP::symmetric }, + { u"\u2A14"_ustr, moOpDF::prefix, 310, 1, 2, moOpDP::movablelargeop | moOpDP::symmetric }, + { u"\u2A15"_ustr, moOpDF::prefix, 310, 1, 2, moOpDP::largeop | moOpDP::symmetric }, + { u"\u2A16"_ustr, moOpDF::prefix, 310, 1, 2, moOpDP::largeop | moOpDP::symmetric }, + { u"\u2A17"_ustr, moOpDF::prefix, 310, 1, 2, moOpDP::largeop | moOpDP::symmetric }, + { u"\u2A18"_ustr, moOpDF::prefix, 310, 1, 2, moOpDP::largeop | moOpDP::symmetric }, + { u"\u2A19"_ustr, moOpDF::prefix, 310, 1, 2, moOpDP::largeop | moOpDP::symmetric }, + { u"\u2A1A"_ustr, moOpDF::prefix, 310, 1, 2, moOpDP::largeop | moOpDP::symmetric }, + { u"\u2A1B"_ustr, moOpDF::prefix, 310, 1, 2, moOpDP::largeop | moOpDP::symmetric }, + { u"\u2A1C"_ustr, moOpDF::prefix, 310, 1, 2, moOpDP::largeop | moOpDP::symmetric }, + { u"\u22C3"_ustr, moOpDF::prefix, 320, 1, 2, moOpDP::movablelargeop | moOpDP::symmetric }, + { u"\u2A03"_ustr, moOpDF::prefix, 320, 1, 2, moOpDP::movablelargeop | moOpDP::symmetric }, + { u"\u2A04"_ustr, moOpDF::prefix, 320, 1, 2, moOpDP::movablelargeop | moOpDP::symmetric }, + { u"\u22C0"_ustr, moOpDF::prefix, 330, 1, 2, moOpDP::movablelargeop | moOpDP::symmetric }, + { u"\u22C1"_ustr, moOpDF::prefix, 330, 1, 2, moOpDP::movablelargeop | moOpDP::symmetric }, + { u"\u22C2"_ustr, moOpDF::prefix, 330, 1, 2, moOpDP::movablelargeop | moOpDP::symmetric }, + { u"\u2A00"_ustr, moOpDF::prefix, 330, 1, 2, moOpDP::movablelargeop | moOpDP::symmetric }, + { u"\u2A02"_ustr, moOpDF::prefix, 330, 1, 2, moOpDP::movablelargeop | moOpDP::symmetric }, + { u"\u2A05"_ustr, moOpDF::prefix, 330, 1, 2, moOpDP::movablelargeop | moOpDP::symmetric }, + { u"\u2A06"_ustr, moOpDF::prefix, 330, 1, 2, moOpDP::movablelargeop | moOpDP::symmetric }, + { u"\u2A07"_ustr, moOpDF::prefix, 330, 1, 2, moOpDP::movablelargeop | moOpDP::symmetric }, + { u"\u2A08"_ustr, moOpDF::prefix, 330, 1, 2, moOpDP::movablelargeop | moOpDP::symmetric }, + { u"\u2A09"_ustr, moOpDF::prefix, 330, 1, 2, moOpDP::movablelargeop | moOpDP::symmetric }, + { u"\u2AFC"_ustr, moOpDF::prefix, 330, 1, 2, moOpDP::movablelargeop | moOpDP::symmetric }, + { u"\u2AFF"_ustr, moOpDF::prefix, 330, 1, 2, moOpDP::movablelargeop | moOpDP::symmetric }, + { u"\u2240"_ustr, moOpDF::infix, 340, 4, 4, moOpDP::nonedp }, + { u"\u220F"_ustr, moOpDF::prefix, 350, 1, 2, moOpDP::movablelargeop | moOpDP::symmetric }, + { u"\u2210"_ustr, moOpDF::prefix, 350, 1, 2, moOpDP::movablelargeop | moOpDP::symmetric }, + { u"\u2229"_ustr, moOpDF::infix, 350, 4, 4, moOpDP::nonedp }, + { u"\u222A"_ustr, moOpDF::infix, 350, 4, 4, moOpDP::nonedp }, + { u"*"_ustr, moOpDF::infix, 390, 3, 3, moOpDP::nonedp }, + { u"."_ustr, moOpDF::infix, 390, 3, 3, moOpDP::nonedp }, + { u"\u00D7"_ustr, moOpDF::infix, 390, 4, 4, moOpDP::nonedp }, + { u"\u2022"_ustr, moOpDF::infix, 390, 4, 4, moOpDP::nonedp }, + { u"\u2043"_ustr, moOpDF::infix, 390, 4, 4, moOpDP::nonedp }, + { u"\u2062"_ustr, moOpDF::infix, 390, 0, 0, moOpDP::nonedp }, + { u"\u22A0"_ustr, moOpDF::infix, 390, 4, 4, moOpDP::nonedp }, + { u"\u22A1"_ustr, moOpDF::infix, 390, 4, 4, moOpDP::nonedp }, + { u"\u22C5"_ustr, moOpDF::infix, 390, 4, 4, moOpDP::nonedp }, + { u"\u2A2F"_ustr, moOpDF::infix, 390, 4, 4, moOpDP::nonedp }, + { u"\u2A3F"_ustr, moOpDF::infix, 390, 4, 4, moOpDP::nonedp }, + { u"\u00B7"_ustr, moOpDF::infix, 400, 4, 4, moOpDP::nonedp }, + { u"\u2297"_ustr, moOpDF::infix, 410, 4, 4, moOpDP::nonedp }, + { u"%"_ustr, moOpDF::infix, 640, 3, 3, moOpDP::nonedp }, + { u"\\"_ustr, moOpDF::infix, 650, 0, 0, moOpDP::nonedp }, + { u"\u2216"_ustr, moOpDF::infix, 650, 4, 4, moOpDP::nonedp }, + { u"/"_ustr, moOpDF::infix, 660, 1, 1, moOpDP::nonedp }, + { u"\u00F7"_ustr, moOpDF::infix, 660, 4, 4, moOpDP::nonedp }, + { u"\u2220"_ustr, moOpDF::prefix, 670, 0, 0, moOpDP::nonedp }, + { u"\u2221"_ustr, moOpDF::prefix, 670, 0, 0, moOpDP::nonedp }, + { u"\u2222"_ustr, moOpDF::prefix, 670, 0, 0, moOpDP::nonedp }, + { u"\u00AC"_ustr, moOpDF::prefix, 680, 2, 1, moOpDP::nonedp }, + { u"\u2299"_ustr, moOpDF::infix, 710, 4, 4, moOpDP::nonedp }, + { u"\u2202"_ustr, moOpDF::prefix, 740, 2, 1, moOpDP::nonedp }, + { u"\u2207"_ustr, moOpDF::prefix, 740, 2, 1, moOpDP::nonedp }, + { u"**"_ustr, moOpDF::infix, 780, 1, 1, moOpDP::nonedp }, + { u"<>"_ustr, moOpDF::infix, 780, 1, 1, moOpDP::nonedp }, + { u"^"_ustr, moOpDF::infix, 780, 1, 1, moOpDP::nonedp }, + { u"\u2032"_ustr, moOpDF::postfix, 800, 0, 0, moOpDP::nonedp }, + { u"\u266D"_ustr, moOpDF::postfix, 800, 0, 2, moOpDP::nonedp }, + { u"\u266E"_ustr, moOpDF::postfix, 800, 0, 2, moOpDP::nonedp }, + { u"\u266F"_ustr, moOpDF::postfix, 800, 0, 2, moOpDP::nonedp }, + { u"!"_ustr, moOpDF::postfix, 810, 1, 0, moOpDP::nonedp }, + { u"!!"_ustr, moOpDF::postfix, 810, 1, 0, moOpDP::nonedp }, + { u"//"_ustr, moOpDF::infix, 820, 1, 1, moOpDP::nonedp }, + { u"@"_ustr, moOpDF::infix, 825, 1, 1, moOpDP::nonedp }, + { u"?"_ustr, moOpDF::infix, 835, 1, 1, moOpDP::nonedp }, + { u"\u2145"_ustr, moOpDF::prefix, 845, 2, 1, moOpDP::nonedp }, + { u"\u2146"_ustr, moOpDF::prefix, 845, 2, 0, moOpDP::nonedp }, + { u"\u221A"_ustr, moOpDF::prefix, 845, 1, 1, moOpDP::stretchy }, + { u"\u221B"_ustr, moOpDF::prefix, 845, 1, 1, moOpDP::nonedp }, + { u"\u221C"_ustr, moOpDF::prefix, 845, 1, 1, moOpDP::nonedp }, + { u"\u2061"_ustr, moOpDF::infix, 850, 0, 0, moOpDP::nonedp }, + { u"\""_ustr, moOpDF::postfix, 880, 0, 0, moOpDP::accent }, + { u"&"_ustr, moOpDF::postfix, 880, 0, 0, moOpDP::nonedp }, + { u"\'"_ustr, moOpDF::postfix, 880, 0, 0, moOpDP::accent }, + { u"++"_ustr, moOpDF::postfix, 880, 0, 0, moOpDP::nonedp }, + { u"--"_ustr, moOpDF::postfix, 880, 0, 0, moOpDP::nonedp }, + { u"^"_ustr, moOpDF::postfix, 880, 0, 0, moOpDP::stretchy | moOpDP::accent }, + { u"_"_ustr, moOpDF::postfix, 880, 0, 0, moOpDP::stretchy | moOpDP::accent }, + { u"`"_ustr, moOpDF::postfix, 880, 0, 00, moOpDP::accent }, + { u"~"_ustr, moOpDF::postfix, 880, 0, 0, moOpDP::stretchy | moOpDP::accent }, + { u"\u00A8"_ustr, moOpDF::postfix, 880, 0, 0, moOpDP::accent }, + { u"\u00AA"_ustr, moOpDF::postfix, 880, 0, 0, moOpDP::accent }, + { u"\u00AF"_ustr, moOpDF::postfix, 880, 0, 0, moOpDP::stretchy | moOpDP::accent }, + { u"\u00B0"_ustr, moOpDF::postfix, 880, 0, 0, moOpDP::nonedp }, + { u"\u00B2"_ustr, moOpDF::postfix, 880, 0, 0, moOpDP::accent }, + { u"\u00B3"_ustr, moOpDF::postfix, 880, 0, 0, moOpDP::accent }, + { u"\u00B4"_ustr, moOpDF::postfix, 880, 0, 0, moOpDP::accent }, + { u"\u00B8"_ustr, moOpDF::postfix, 880, 0, 0, moOpDP::accent }, + { u"\u00B9"_ustr, moOpDF::postfix, 880, 0, 0, moOpDP::accent }, + { u"\u00BA"_ustr, moOpDF::postfix, 880, 0, 0, moOpDP::accent }, + { u"\u02C6"_ustr, moOpDF::postfix, 880, 0, 0, moOpDP::stretchy | moOpDP::accent }, + { u"\u02C7"_ustr, moOpDF::postfix, 880, 0, 0, moOpDP::stretchy | moOpDP::accent }, + { u"\u02C9"_ustr, moOpDF::postfix, 880, 0, 0, moOpDP::stretchy | moOpDP::accent }, + { u"\u02CA"_ustr, moOpDF::postfix, 880, 0, 0, moOpDP::accent }, + { u"\u02CB"_ustr, moOpDF::postfix, 880, 0, 0, moOpDP::accent }, + { u"\u02CD"_ustr, moOpDF::postfix, 880, 0, 0, moOpDP::stretchy | moOpDP::accent }, + { u"\u02D8"_ustr, moOpDF::postfix, 880, 0, 0, moOpDP::accent }, + { u"\u02D9"_ustr, moOpDF::postfix, 880, 0, 0, moOpDP::accent }, + { u"\u02DA"_ustr, moOpDF::postfix, 880, 0, 0, moOpDP::accent }, + { u"\u02DC"_ustr, moOpDF::postfix, 880, 0, 0, moOpDP::stretchy | moOpDP::accent }, + { u"\u02DD"_ustr, moOpDF::postfix, 880, 0, 0, moOpDP::accent }, + { u"\u02F7"_ustr, moOpDF::postfix, 880, 0, 0, moOpDP::stretchy | moOpDP::accent }, + { u"\u0302"_ustr, moOpDF::postfix, 880, 0, 0, moOpDP::stretchy | moOpDP::accent }, + { u"\u0311"_ustr, moOpDF::postfix, 880, 0, 0, moOpDP::accent }, + { u"\u201A"_ustr, moOpDF::postfix, 880, 0, 0, moOpDP::accent }, + { u"\u201B"_ustr, moOpDF::postfix, 880, 0, 0, moOpDP::accent }, + { u"\u201E"_ustr, moOpDF::postfix, 880, 0, 0, moOpDP::accent }, + { u"\u201F"_ustr, moOpDF::postfix, 880, 0, 0, moOpDP::accent }, + { u"\u2033"_ustr, moOpDF::postfix, 880, 0, 0, moOpDP::accent }, + { u"\u2034"_ustr, moOpDF::postfix, 880, 0, 0, moOpDP::accent }, + { u"\u2035"_ustr, moOpDF::postfix, 880, 0, 0, moOpDP::accent }, + { u"\u2036"_ustr, moOpDF::postfix, 880, 0, 0, moOpDP::accent }, + { u"\u2037"_ustr, moOpDF::postfix, 880, 0, 0, moOpDP::accent }, + { u"\u203E"_ustr, moOpDF::postfix, 880, 0, 0, moOpDP::stretchy | moOpDP::accent }, + { u"\u2057"_ustr, moOpDF::postfix, 880, 0, 0, moOpDP::accent }, + { u"\u2064"_ustr, moOpDF::infix, 880, 0, 0, moOpDP::nonedp }, + { u"\u20DB"_ustr, moOpDF::postfix, 880, 0, 0, moOpDP::accent }, + { u"\u20DC"_ustr, moOpDF::postfix, 880, 0, 0, moOpDP::accent }, + { u"\u23B4"_ustr, moOpDF::postfix, 880, 0, 0, moOpDP::stretchy | moOpDP::accent }, + { u"\u23B5"_ustr, moOpDF::postfix, 880, 0, 0, moOpDP::stretchy | moOpDP::accent }, + { u"\u23DC"_ustr, moOpDF::postfix, 880, 0, 0, moOpDP::stretchy | moOpDP::accent }, + { u"\u23DD"_ustr, moOpDF::postfix, 880, 0, 0, moOpDP::stretchy | moOpDP::accent }, + { u"\u23DE"_ustr, moOpDF::postfix, 880, 0, 0, moOpDP::stretchy | moOpDP::accent }, + { u"\u23DF"_ustr, moOpDF::postfix, 880, 0, 0, moOpDP::stretchy | moOpDP::accent }, + { u"\u23E0"_ustr, moOpDF::postfix, 880, 0, 0, moOpDP::stretchy | moOpDP::accent }, + { u"\u23E1"_ustr, moOpDF::postfix, 880, 0, 0, moOpDP::stretchy | moOpDP::accent }, + { u"_"_ustr, moOpDF::infix, 900, 1, 1, moOpDP::nonedp } }; + +std::vector<moOperatorData> starmathdatabase::moOperatorDataDictionary( + moOperatorDataDictionaryData, moOperatorDataDictionaryData + starmathdatabase::MATHML_MO_COUNT); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/mathml/mathmlattr.cxx b/starmath/source/mathml/mathmlattr.cxx new file mode 100644 index 0000000000..5e54f0d92e --- /dev/null +++ b/starmath/source/mathml/mathmlattr.cxx @@ -0,0 +1,165 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <mathmlattr.hxx> + +#include <o3tl/safeint.hxx> +#include <o3tl/string_view.hxx> +#include <rtl/math.h> + +#include <cstddef> +#include <string_view> +#include <unordered_map> + +static std::size_t ParseMathMLUnsignedNumber(std::u16string_view rStr, Fraction& rUN) +{ + auto nLen = rStr.length(); + std::size_t nDecimalPoint = std::u16string_view::npos; + std::size_t nIdx; + sal_Int64 nom = 0; + sal_Int64 den = 1; + bool validNomDen = true; + for (nIdx = 0; nIdx < nLen; nIdx++) + { + auto cD = rStr[nIdx]; + if (cD == u'.') + { + if (nDecimalPoint != std::u16string_view::npos) + return std::u16string_view::npos; + nDecimalPoint = nIdx; + continue; + } + if (cD < u'0' || u'9' < cD) + break; + if (validNomDen + && (o3tl::checked_multiply(nom, sal_Int64(10), nom) + || o3tl::checked_add(nom, sal_Int64(cD - u'0'), nom) + || nom >= std::numeric_limits<sal_Int32>::max() + || (nDecimalPoint != std::u16string_view::npos + && o3tl::checked_multiply(den, sal_Int64(10), den)))) + { + validNomDen = false; + } + } + if (nIdx == 0 || (nIdx == 1 && nDecimalPoint == 0)) + return std::u16string_view::npos; + + // If the input "xx.yyy" can be represented with nom = xx*10^n + yyy and den = 10^n in sal_Int64 + // (where n is the length of "yyy"), then use that to create an accurate Fraction (and TODO: we + // could even ignore trailing "0" characters in "yyy", for a smaller n and thus a greater chance + // of validNomDen); if not, use the less accurate approach of creating a Fraction from double: + if (validNomDen) + { + rUN = Fraction(nom, den); + } + else + { + rUN = Fraction( + rtl_math_uStringToDouble(rStr.data(), rStr.data() + nIdx, '.', 0, nullptr, nullptr)); + } + + return nIdx; +} + +static std::size_t ParseMathMLNumber(std::u16string_view rStr, Fraction& rN) +{ + if (rStr.empty()) + return std::u16string_view::npos; + bool bNegative = (rStr[0] == '-'); + std::size_t nOffset = bNegative ? 1 : 0; + auto nIdx = ParseMathMLUnsignedNumber(rStr.substr(nOffset), rN); + if (nIdx == std::u16string_view::npos || !rN.IsValid()) + return std::u16string_view::npos; + if (bNegative) + rN *= -1; + return nOffset + nIdx; +} + +bool ParseMathMLAttributeLengthValue(std::u16string_view rStr, MathMLAttributeLengthValue& rV) +{ + auto nIdx = ParseMathMLNumber(rStr, rV.aNumber); + if (nIdx == std::u16string_view::npos) + return false; + std::u16string_view sRest = rStr.substr(nIdx); + if (sRest.empty()) + { + rV.eUnit = MathMLLengthUnit::None; + } + if (o3tl::starts_with(sRest, u"em")) + { + rV.eUnit = MathMLLengthUnit::Em; + } + if (o3tl::starts_with(sRest, u"ex")) + { + rV.eUnit = MathMLLengthUnit::Ex; + } + if (o3tl::starts_with(sRest, u"px")) + { + rV.eUnit = MathMLLengthUnit::Px; + } + if (o3tl::starts_with(sRest, u"in")) + { + rV.eUnit = MathMLLengthUnit::In; + } + if (o3tl::starts_with(sRest, u"cm")) + { + rV.eUnit = MathMLLengthUnit::Cm; + } + if (o3tl::starts_with(sRest, u"mm")) + { + rV.eUnit = MathMLLengthUnit::Mm; + } + if (o3tl::starts_with(sRest, u"pt")) + { + rV.eUnit = MathMLLengthUnit::Pt; + } + if (o3tl::starts_with(sRest, u"pc")) + { + rV.eUnit = MathMLLengthUnit::Pc; + } + if (sRest[0] == u'%') + { + rV.eUnit = MathMLLengthUnit::Percent; + } + return true; +} + +bool GetMathMLMathvariantValue(const OUString& rStr, MathMLMathvariantValue& rV) +{ + static const std::unordered_map<OUString, MathMLMathvariantValue> aMap{ + { "normal", MathMLMathvariantValue::Normal }, + { "bold", MathMLMathvariantValue::Bold }, + { "italic", MathMLMathvariantValue::Italic }, + { "bold-italic", MathMLMathvariantValue::BoldItalic }, + { "double-struck", MathMLMathvariantValue::DoubleStruck }, + { "bold-fraktur", MathMLMathvariantValue::BoldFraktur }, + { "script", MathMLMathvariantValue::Script }, + { "bold-script", MathMLMathvariantValue::BoldScript }, + { "fraktur", MathMLMathvariantValue::Fraktur }, + { "sans-serif", MathMLMathvariantValue::SansSerif }, + { "bold-sans-serif", MathMLMathvariantValue::BoldSansSerif }, + { "sans-serif-italic", MathMLMathvariantValue::SansSerifItalic }, + { "sans-serif-bold-italic", MathMLMathvariantValue::SansSerifBoldItalic }, + { "monospace", MathMLMathvariantValue::Monospace }, + { "initial", MathMLMathvariantValue::Initial }, + { "tailed", MathMLMathvariantValue::Tailed }, + { "looped", MathMLMathvariantValue::Looped }, + { "stretched", MathMLMathvariantValue::Stretched } + }; + + auto it = aMap.find(rStr); + if (it != aMap.end()) + { + rV = it->second; + return true; + } + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/starmath/source/mathml/mathmlexport.cxx b/starmath/source/mathml/mathmlexport.cxx new file mode 100644 index 0000000000..1c18e716e7 --- /dev/null +++ b/starmath/source/mathml/mathmlexport.cxx @@ -0,0 +1,1443 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +/* + Warning: The SvXMLElementExport helper class creates the beginning and + closing tags of xml elements in its constructor and destructor, so there's + hidden stuff going on, on occasion the ordering of these classes declarations + may be significant +*/ + +#include <com/sun/star/xml/sax/Writer.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/embed/ElementModes.hpp> +#include <com/sun/star/util/MeasureUnit.hpp> +#include <com/sun/star/task/XStatusIndicator.hpp> +#include <com/sun/star/uno/Any.h> + +#include <officecfg/Office/Common.hxx> +#include <rtl/math.hxx> +#include <sfx2/frame.hxx> +#include <sfx2/docfile.hxx> +#include <sfx2/sfxsids.hrc> +#include <osl/diagnose.h> +#include <sot/storage.hxx> +#include <svl/itemset.hxx> +#include <svl/stritem.hxx> +#include <comphelper/fileformat.h> +#include <comphelper/processfactory.hxx> +#include <unotools/streamwrap.hxx> +#include <sax/tools/converter.hxx> +#include <xmloff/xmlnamespace.hxx> +#include <xmloff/xmltoken.hxx> +#include <xmloff/namespacemap.hxx> +#include <comphelper/genericpropertyset.hxx> +#include <comphelper/servicehelper.hxx> +#include <comphelper/propertysetinfo.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <sal/log.hxx> + +#include <stack> + +#include <mathmlexport.hxx> +#include <xparsmlbase.hxx> +#include <strings.hrc> +#include <smmod.hxx> +#include <unomodel.hxx> +#include <document.hxx> +#include <utility.hxx> +#include <cfgitem.hxx> +#include <starmathdatabase.hxx> + +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::document; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star; +using namespace ::xmloff::token; + +namespace +{ +bool IsInPrivateUseArea(sal_uInt32 cChar) { return 0xE000 <= cChar && cChar <= 0xF8FF; } + +sal_uInt32 ConvertMathToMathML(std::u16string_view rText, sal_Int32 nIndex = 0) +{ + auto cRes = o3tl::iterateCodePoints(rText, &nIndex); + if (IsInPrivateUseArea(cRes)) + { + SAL_WARN("starmath", "Error: private use area characters should no longer be in use!"); + cRes = u'@'; // just some character that should easily be notice as odd in the context + } + return cRes; +} +} + +bool SmXMLExportWrapper::Export(SfxMedium& rMedium) +{ + bool bRet = true; + uno::Reference<uno::XComponentContext> xContext(comphelper::getProcessComponentContext()); + + //Get model + uno::Reference<lang::XComponent> xModelComp = xModel; + + bool bEmbedded = false; + SmModel* pModel = comphelper::getFromUnoTunnel<SmModel>(xModel); + + SmDocShell* pDocShell = pModel ? static_cast<SmDocShell*>(pModel->GetObjectShell()) : nullptr; + if (pDocShell && SfxObjectCreateMode::EMBEDDED == pDocShell->GetCreateMode()) + bEmbedded = true; + + uno::Reference<task::XStatusIndicator> xStatusIndicator; + if (!bEmbedded) + { + if (pDocShell /*&& pDocShell->GetMedium()*/) + { + OSL_ENSURE(pDocShell->GetMedium() == &rMedium, "different SfxMedium found"); + + const SfxUnoAnyItem* pItem + = rMedium.GetItemSet().GetItem(SID_PROGRESS_STATUSBAR_CONTROL); + if (pItem) + pItem->GetValue() >>= xStatusIndicator; + } + + // set progress range and start status indicator + if (xStatusIndicator.is()) + { + sal_Int32 nProgressRange = bFlat ? 1 : 3; + xStatusIndicator->start(SmResId(STR_STATSTR_WRITING), nProgressRange); + } + } + + static constexpr OUString sUsePrettyPrinting(u"UsePrettyPrinting"_ustr); + static constexpr OUString sBaseURI(u"BaseURI"_ustr); + static constexpr OUString sStreamRelPath(u"StreamRelPath"_ustr); + static constexpr OUString sStreamName(u"StreamName"_ustr); + + // create XPropertySet with three properties for status indicator + static const comphelper::PropertyMapEntry aInfoMap[] = { + { sUsePrettyPrinting, 0, cppu::UnoType<bool>::get(), beans::PropertyAttribute::MAYBEVOID, + 0 }, + { sBaseURI, 0, ::cppu::UnoType<OUString>::get(), beans::PropertyAttribute::MAYBEVOID, 0 }, + { sStreamRelPath, 0, ::cppu::UnoType<OUString>::get(), beans::PropertyAttribute::MAYBEVOID, + 0 }, + { sStreamName, 0, ::cppu::UnoType<OUString>::get(), beans::PropertyAttribute::MAYBEVOID, 0 } + }; + uno::Reference<beans::XPropertySet> xInfoSet( + comphelper::GenericPropertySet_CreateInstance(new comphelper::PropertySetInfo(aInfoMap))); + + bool bUsePrettyPrinting + = bFlat || officecfg::Office::Common::Save::Document::PrettyPrinting::get(); + xInfoSet->setPropertyValue(sUsePrettyPrinting, Any(bUsePrettyPrinting)); + + // Set base URI + xInfoSet->setPropertyValue(sBaseURI, Any(rMedium.GetBaseURL(true))); + + sal_Int32 nSteps = 0; + if (xStatusIndicator.is()) + xStatusIndicator->setValue(nSteps++); + if (!bFlat) //Storage (Package) of Stream + { + uno::Reference<embed::XStorage> xStg = rMedium.GetOutputStorage(); + bool bOASIS = (SotStorage::GetVersion(xStg) > SOFFICE_FILEFORMAT_60); + + // TODO/LATER: handle the case of embedded links gracefully + if (bEmbedded) //&& !pStg->IsRoot() ) + { + OUString aName; + const SfxStringItem* pDocHierarchItem + = rMedium.GetItemSet().GetItem(SID_DOC_HIERARCHICALNAME); + if (pDocHierarchItem) + aName = pDocHierarchItem->GetValue(); + + if (!aName.isEmpty()) + { + xInfoSet->setPropertyValue(sStreamRelPath, Any(aName)); + } + } + + if (!bEmbedded) + { + if (xStatusIndicator.is()) + xStatusIndicator->setValue(nSteps++); + + bRet = WriteThroughComponent(xStg, xModelComp, "meta.xml", xContext, xInfoSet, + (bOASIS ? "com.sun.star.comp.Math.XMLOasisMetaExporter" + : "com.sun.star.comp.Math.XMLMetaExporter")); + } + if (bRet) + { + if (xStatusIndicator.is()) + xStatusIndicator->setValue(nSteps++); + + bRet = WriteThroughComponent(xStg, xModelComp, "content.xml", xContext, xInfoSet, + "com.sun.star.comp.Math.XMLContentExporter"); + } + + if (bRet) + { + if (xStatusIndicator.is()) + xStatusIndicator->setValue(nSteps++); + + bRet = WriteThroughComponent(xStg, xModelComp, "settings.xml", xContext, xInfoSet, + (bOASIS ? "com.sun.star.comp.Math.XMLOasisSettingsExporter" + : "com.sun.star.comp.Math.XMLSettingsExporter")); + } + } + else + { + SvStream* pStream = rMedium.GetOutStream(); + uno::Reference<io::XOutputStream> xOut(new utl::OOutputStreamWrapper(*pStream)); + + if (xStatusIndicator.is()) + xStatusIndicator->setValue(nSteps++); + + bRet = WriteThroughComponent(xOut, xModelComp, xContext, xInfoSet, + "com.sun.star.comp.Math.XMLContentExporter"); + } + + if (xStatusIndicator.is()) + xStatusIndicator->end(); + + return bRet; +} + +/// export through an XML exporter component (output stream version) +bool SmXMLExportWrapper::WriteThroughComponent(const Reference<io::XOutputStream>& xOutputStream, + const Reference<XComponent>& xComponent, + Reference<uno::XComponentContext> const& rxContext, + Reference<beans::XPropertySet> const& rPropSet, + const char* pComponentName) +{ + OSL_ENSURE(xOutputStream.is(), "I really need an output stream!"); + OSL_ENSURE(xComponent.is(), "Need component!"); + OSL_ENSURE(nullptr != pComponentName, "Need component name!"); + + // get component + Reference<xml::sax::XWriter> xSaxWriter = xml::sax::Writer::create(rxContext); + + // connect XML writer to output stream + xSaxWriter->setOutputStream(xOutputStream); + if (m_bUseHTMLMLEntities) + xSaxWriter->setCustomEntityNames(starmathdatabase::icustomMathmlHtmlEntitiesExport); + + // prepare arguments (prepend doc handler to given arguments) + Sequence<Any> aArgs{ Any(xSaxWriter), Any(rPropSet) }; + + // get filter component + Reference<document::XExporter> xExporter( + rxContext->getServiceManager()->createInstanceWithArgumentsAndContext( + OUString::createFromAscii(pComponentName), aArgs, rxContext), + UNO_QUERY); + OSL_ENSURE(xExporter.is(), "can't instantiate export filter component"); + if (!xExporter.is()) + return false; + + // connect model and filter + xExporter->setSourceDocument(xComponent); + + // filter! + Reference<XFilter> xFilter(xExporter, UNO_QUERY); + uno::Sequence<PropertyValue> aProps(0); + xFilter->filter(aProps); + + auto pFilter = dynamic_cast<SmXMLExport*>(xFilter.get()); + return pFilter == nullptr || pFilter->GetSuccess(); +} + +/// export through an XML exporter component (storage version) +bool SmXMLExportWrapper::WriteThroughComponent(const Reference<embed::XStorage>& xStorage, + const Reference<XComponent>& xComponent, + const char* pStreamName, + Reference<uno::XComponentContext> const& rxContext, + Reference<beans::XPropertySet> const& rPropSet, + const char* pComponentName) +{ + OSL_ENSURE(xStorage.is(), "Need storage!"); + OSL_ENSURE(nullptr != pStreamName, "Need stream name!"); + + // open stream + Reference<io::XStream> xStream; + OUString sStreamName = OUString::createFromAscii(pStreamName); + try + { + xStream = xStorage->openStreamElement(sStreamName, embed::ElementModes::READWRITE + | embed::ElementModes::TRUNCATE); + } + catch (const uno::Exception&) + { + DBG_UNHANDLED_EXCEPTION("starmath", "Can't create output stream in package"); + return false; + } + + uno::Reference<beans::XPropertySet> xSet(xStream, uno::UNO_QUERY); + static constexpr OUStringLiteral sMediaType = u"MediaType"; + static constexpr OUStringLiteral sTextXml = u"text/xml"; + xSet->setPropertyValue(sMediaType, Any(OUString(sTextXml))); + + // all streams must be encrypted in encrypted document + static constexpr OUStringLiteral sUseCommonStoragePasswordEncryption + = u"UseCommonStoragePasswordEncryption"; + xSet->setPropertyValue(sUseCommonStoragePasswordEncryption, Any(true)); + + // set Base URL + if (rPropSet.is()) + { + rPropSet->setPropertyValue("StreamName", Any(sStreamName)); + } + + // write the stuff + bool bRet = WriteThroughComponent(xStream->getOutputStream(), xComponent, rxContext, rPropSet, + pComponentName); + + return bRet; +} + +SmXMLExport::SmXMLExport(const css::uno::Reference<css::uno::XComponentContext>& rContext, + OUString const& implementationName, SvXMLExportFlags nExportFlags) + : SvXMLExport(rContext, implementationName, util::MeasureUnit::INCH, XML_MATH, nExportFlags) + , pTree(nullptr) + , bSuccess(false) +{ +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +Math_XMLExporter_get_implementation(css::uno::XComponentContext* context, + css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new SmXMLExport(context, "com.sun.star.comp.Math.XMLExporter", + SvXMLExportFlags::OASIS | SvXMLExportFlags::ALL)); +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +Math_XMLMetaExporter_get_implementation(css::uno::XComponentContext* context, + css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire( + new SmXMLExport(context, "com.sun.star.comp.Math.XMLMetaExporter", SvXMLExportFlags::META)); +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +Math_XMLOasisMetaExporter_get_implementation(css::uno::XComponentContext* context, + css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new SmXMLExport(context, "com.sun.star.comp.Math.XMLOasisMetaExporter", + SvXMLExportFlags::OASIS | SvXMLExportFlags::META)); +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +Math_XMLSettingsExporter_get_implementation(css::uno::XComponentContext* context, + css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new SmXMLExport(context, "com.sun.star.comp.Math.XMLSettingsExporter", + SvXMLExportFlags::SETTINGS)); +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +Math_XMLOasisSettingsExporter_get_implementation(css::uno::XComponentContext* context, + css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new SmXMLExport(context, "com.sun.star.comp.Math.XMLOasisSettingsExporter", + SvXMLExportFlags::OASIS | SvXMLExportFlags::SETTINGS)); +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +Math_XMLContentExporter_get_implementation(css::uno::XComponentContext* context, + css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new SmXMLExport(context, "com.sun.star.comp.Math.XMLContentExporter", + SvXMLExportFlags::OASIS | SvXMLExportFlags::CONTENT)); +} + +ErrCode SmXMLExport::exportDoc(enum XMLTokenEnum eClass) +{ + if (!(getExportFlags() & SvXMLExportFlags::CONTENT)) + { + SvXMLExport::exportDoc(eClass); + } + else + { + uno::Reference<frame::XModel> xModel = GetModel(); + SmModel* pModel = comphelper::getFromUnoTunnel<SmModel>(xModel); + + if (pModel) + { + SmDocShell* pDocShell = static_cast<SmDocShell*>(pModel->GetObjectShell()); + pTree = pDocShell->GetFormulaTree(); + aText = pDocShell->GetText(); + } + + GetDocHandler()->startDocument(); + + addChaffWhenEncryptedStorage(); + + /*Add xmlns line*/ + comphelper::AttributeList& rList = GetAttrList(); + + // make use of a default namespace + ResetNamespaceMap(); // Math doesn't need namespaces from xmloff, since it now uses default namespaces (because that is common with current MathML usage in the web) + GetNamespaceMap_().Add(OUString(), GetXMLToken(XML_N_MATH), XML_NAMESPACE_MATH); + + rList.AddAttribute(GetNamespaceMap().GetAttrNameByKey(XML_NAMESPACE_MATH), + GetNamespaceMap().GetNameByKey(XML_NAMESPACE_MATH)); + + //I think we need something like ImplExportEntities(); + ExportContent_(); + GetDocHandler()->endDocument(); + } + + bSuccess = true; + return ERRCODE_NONE; +} + +void SmXMLExport::ExportContent_() +{ + uno::Reference<frame::XModel> xModel = GetModel(); + SmModel* pModel = comphelper::getFromUnoTunnel<SmModel>(xModel); + SmDocShell* pDocShell = pModel ? static_cast<SmDocShell*>(pModel->GetObjectShell()) : nullptr; + OSL_ENSURE(pDocShell, "doc shell missing"); + + if (pDocShell) + { + if (!pDocShell->GetFormat().IsTextmode()) + { + // If the Math equation is not in text mode, we attach a display="block" + // attribute on the <math> root. We don't do anything if it is in + // text mode, the default display="inline" value will be used. + AddAttribute(XML_NAMESPACE_MATH, XML_DISPLAY, XML_BLOCK); + } + if (pDocShell->GetFormat().IsRightToLeft()) + { + // If the Math equation is set right-to-left, we attach a dir="rtl" + // attribute on the <math> root. We don't do anything if it is set + // left-to-right, the default dir="ltr" value will be used. + AddAttribute(XML_NAMESPACE_MATH, XML_DIR, XML_RTL); + } + } + + SvXMLElementExport aEquation(*this, XML_NAMESPACE_MATH, XML_MATH, true, true); + std::unique_ptr<SvXMLElementExport> pSemantics; + + if (!aText.isEmpty()) + { + pSemantics.reset( + new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_SEMANTICS, true, true)); + } + + ExportNodes(pTree, 0); + + if (aText.isEmpty()) + return; + + SmModule* pMod = SM_MOD(); + sal_Int16 nSmSyntaxVersion = pMod->GetConfig()->GetDefaultSmSyntaxVersion(); + + // Convert symbol names + if (pDocShell) + { + nSmSyntaxVersion = pDocShell->GetSmSyntaxVersion(); + AbstractSmParser* rParser = pDocShell->GetParser(); + bool bVal = rParser->IsExportSymbolNames(); + rParser->SetExportSymbolNames(true); + auto pTmpTree = rParser->Parse(aText); + aText = rParser->GetText(); + pTmpTree.reset(); + rParser->SetExportSymbolNames(bVal); + } + + OUStringBuffer sStrBuf(12); + sStrBuf.append(u"StarMath "); + if (nSmSyntaxVersion == 5) + sStrBuf.append(u"5.0"); + else + sStrBuf.append(static_cast<sal_Int32>(nSmSyntaxVersion)); + + AddAttribute(XML_NAMESPACE_MATH, XML_ENCODING, sStrBuf.makeStringAndClear()); + SvXMLElementExport aAnnotation(*this, XML_NAMESPACE_MATH, XML_ANNOTATION, true, false); + GetDocHandler()->characters(aText); +} + +void SmXMLExport::GetViewSettings(Sequence<PropertyValue>& aProps) +{ + uno::Reference<frame::XModel> xModel = GetModel(); + if (!xModel.is()) + return; + + SmModel* pModel = comphelper::getFromUnoTunnel<SmModel>(xModel); + + if (!pModel) + return; + + SmDocShell* pDocShell = static_cast<SmDocShell*>(pModel->GetObjectShell()); + if (!pDocShell) + return; + + aProps.realloc(4); + PropertyValue* pValue = aProps.getArray(); + sal_Int32 nIndex = 0; + + tools::Rectangle aRect(pDocShell->GetVisArea()); + + pValue[nIndex].Name = "ViewAreaTop"; + pValue[nIndex++].Value <<= aRect.Top(); + + pValue[nIndex].Name = "ViewAreaLeft"; + pValue[nIndex++].Value <<= aRect.Left(); + + pValue[nIndex].Name = "ViewAreaWidth"; + pValue[nIndex++].Value <<= aRect.GetWidth(); + + pValue[nIndex].Name = "ViewAreaHeight"; + pValue[nIndex++].Value <<= aRect.GetHeight(); +} + +void SmXMLExport::GetConfigurationSettings(Sequence<PropertyValue>& rProps) +{ + Reference<XPropertySet> xProps(GetModel(), UNO_QUERY); + if (!xProps.is()) + return; + + Reference<XPropertySetInfo> xPropertySetInfo = xProps->getPropertySetInfo(); + if (!xPropertySetInfo.is()) + return; + + const Sequence<Property> aProps = xPropertySetInfo->getProperties(); + const sal_Int32 nCount = aProps.getLength(); + if (!nCount) + return; + + rProps.realloc(nCount); + SmMathConfig* pConfig = SM_MOD()->GetConfig(); + const bool bUsedSymbolsOnly = pConfig && pConfig->IsSaveOnlyUsedSymbols(); + + std::transform(aProps.begin(), aProps.end(), rProps.getArray(), + [bUsedSymbolsOnly, &xProps](const Property& prop) { + PropertyValue aRet; + if (prop.Name != "Formula" && prop.Name != "BasicLibraries" + && prop.Name != "DialogLibraries" && prop.Name != "RuntimeUID") + { + aRet.Name = prop.Name; + OUString aActualName(prop.Name); + // handle 'save used symbols only' + static constexpr OUStringLiteral sUserDefinedSymbolsInUse + = u"UserDefinedSymbolsInUse"; + if (bUsedSymbolsOnly && prop.Name == "Symbols") + aActualName = sUserDefinedSymbolsInUse; + aRet.Value = xProps->getPropertyValue(aActualName); + } + return aRet; + }); +} + +void SmXMLExport::ExportLine(const SmNode* pNode, int nLevel) { ExportExpression(pNode, nLevel); } + +void SmXMLExport::ExportBinaryHorizontal(const SmNode* pNode, int nLevel) +{ + TG nGroup = pNode->GetToken().nGroup; + + SvXMLElementExport aRow(*this, XML_NAMESPACE_MATH, XML_MROW, true, true); + + // Unfold the binary tree structure as long as the nodes are SmBinHorNode + // with the same nGroup. This will reduce the number of nested <mrow> + // elements e.g. we only need three <mrow> levels to export + + // "a*b*c*d+e*f*g*h+i*j*k*l = a*b*c*d+e*f*g*h+i*j*k*l = + // a*b*c*d+e*f*g*h+i*j*k*l = a*b*c*d+e*f*g*h+i*j*k*l" + + // See https://www.libreoffice.org/bugzilla/show_bug.cgi?id=66081 + ::std::stack<const SmNode*> s; + s.push(pNode); + while (!s.empty()) + { + const SmNode* node = s.top(); + s.pop(); + if (node->GetType() != SmNodeType::BinHor || node->GetToken().nGroup != nGroup) + { + ExportNodes(node, nLevel + 1); + continue; + } + const SmBinHorNode* binNode = static_cast<const SmBinHorNode*>(node); + s.push(binNode->RightOperand()); + s.push(binNode->Symbol()); + s.push(binNode->LeftOperand()); + } +} + +void SmXMLExport::ExportUnaryHorizontal(const SmNode* pNode, int nLevel) +{ + ExportExpression(pNode, nLevel); +} + +void SmXMLExport::ExportExpression(const SmNode* pNode, int nLevel, + bool bNoMrowContainer /*=false*/) +{ + std::unique_ptr<SvXMLElementExport> pRow; + size_t nSize = pNode->GetNumSubNodes(); + + // #i115443: nodes of type expression always need to be grouped with mrow statement + if (!bNoMrowContainer && (nSize > 1 || pNode->GetType() == SmNodeType::Expression)) + pRow.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MROW, true, true)); + + for (size_t i = 0; i < nSize; ++i) + { + if (const SmNode* pTemp = pNode->GetSubNode(i)) + ExportNodes(pTemp, nLevel + 1); + } +} + +void SmXMLExport::ExportBinaryVertical(const SmNode* pNode, int nLevel) +{ + assert(pNode->GetNumSubNodes() == 3); + const SmNode* pNum = pNode->GetSubNode(0); + const SmNode* pDenom = pNode->GetSubNode(2); + if (pNum->GetType() == SmNodeType::Align && pNum->GetToken().eType != TALIGNC) + { + // A left or right alignment is specified on the numerator: + // attach the corresponding numalign attribute. + AddAttribute(XML_NAMESPACE_MATH, XML_NUMALIGN, + pNum->GetToken().eType == TALIGNL ? XML_LEFT : XML_RIGHT); + } + if (pDenom->GetType() == SmNodeType::Align && pDenom->GetToken().eType != TALIGNC) + { + // A left or right alignment is specified on the denominator: + // attach the corresponding denomalign attribute. + AddAttribute(XML_NAMESPACE_MATH, XML_DENOMALIGN, + pDenom->GetToken().eType == TALIGNL ? XML_LEFT : XML_RIGHT); + } + SvXMLElementExport aFraction(*this, XML_NAMESPACE_MATH, XML_MFRAC, true, true); + ExportNodes(pNum, nLevel); + ExportNodes(pDenom, nLevel); +} + +void SmXMLExport::ExportBinaryDiagonal(const SmNode* pNode, int nLevel) +{ + assert(pNode->GetNumSubNodes() == 3); + + if (pNode->GetToken().eType == TWIDESLASH) + { + // wideslash + // export the node as <mfrac bevelled="true"> + AddAttribute(XML_NAMESPACE_MATH, XML_BEVELLED, XML_TRUE); + SvXMLElementExport aFraction(*this, XML_NAMESPACE_MATH, XML_MFRAC, true, true); + ExportNodes(pNode->GetSubNode(0), nLevel); + ExportNodes(pNode->GetSubNode(1), nLevel); + } + else + { + // widebslash + // We can not use <mfrac> to a backslash, so just use <mo>\</mo> + SvXMLElementExport aRow(*this, XML_NAMESPACE_MATH, XML_MROW, true, true); + + ExportNodes(pNode->GetSubNode(0), nLevel); + + { // Scoping for <mo> creation + SvXMLElementExport aMo(*this, XML_NAMESPACE_MATH, XML_MO, true, true); + GetDocHandler()->characters(OUStringChar(MS_BACKSLASH)); + } + + ExportNodes(pNode->GetSubNode(1), nLevel); + } +} + +void SmXMLExport::ExportTable(const SmNode* pNode, int nLevel) +{ + std::unique_ptr<SvXMLElementExport> pTable; + + size_t nSize = pNode->GetNumSubNodes(); + + //If the list ends in newline then the last entry has + //no subnodes, the newline is superfluous so we just drop + //the last node, inclusion would create a bad MathML + //table + if (nSize >= 1) + { + const SmNode* pLine = pNode->GetSubNode(nSize - 1); + if (pLine->GetType() == SmNodeType::Line && pLine->GetNumSubNodes() == 1 + && pLine->GetSubNode(0) != nullptr + && pLine->GetSubNode(0)->GetToken().eType == TNEWLINE) + --nSize; + } + + // try to avoid creating a mtable element when the formula consists only + // of a single output line + if (nLevel || (nSize > 1)) + pTable.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MTABLE, true, true)); + + for (size_t i = 0; i < nSize; ++i) + { + if (const SmNode* pTemp = pNode->GetSubNode(i)) + { + std::unique_ptr<SvXMLElementExport> pRow; + std::unique_ptr<SvXMLElementExport> pCell; + if (pTable) + { + pRow.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MTR, true, true)); + SmTokenType eAlign = TALIGNC; + if (pTemp->GetType() == SmNodeType::Align) + { + // For Binom() and Stack() constructions, the SmNodeType::Align nodes + // are direct children. + // binom{alignl ...}{alignr ...} and + // stack{alignl ... ## alignr ... ## ...} + eAlign = pTemp->GetToken().eType; + } + else if (pTemp->GetType() == SmNodeType::Line && pTemp->GetNumSubNodes() == 1 + && pTemp->GetSubNode(0) + && pTemp->GetSubNode(0)->GetType() == SmNodeType::Align) + { + // For the Table() construction, the SmNodeType::Align node is a child + // of an SmNodeType::Line node. + // alignl ... newline alignr ... newline ... + eAlign = pTemp->GetSubNode(0)->GetToken().eType; + } + if (eAlign != TALIGNC) + { + // If a left or right alignment is specified on this line, + // attach the corresponding columnalign attribute. + AddAttribute(XML_NAMESPACE_MATH, XML_COLUMNALIGN, + eAlign == TALIGNL ? XML_LEFT : XML_RIGHT); + } + pCell.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MTD, true, true)); + } + ExportNodes(pTemp, nLevel + 1); + } + } +} + +void SmXMLExport::ExportMath(const SmNode* pNode) +{ + const SmTextNode* pTemp = static_cast<const SmTextNode*>(pNode); + std::unique_ptr<SvXMLElementExport> pMath; + + if (pNode->GetType() == SmNodeType::Math || pNode->GetType() == SmNodeType::GlyphSpecial) + { + // Export SmNodeType::Math and SmNodeType::GlyphSpecial symbols as <mo> elements + pMath.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MO, true, false)); + } + else if (pNode->GetType() == SmNodeType::Special) + { + bool bIsItalic = IsItalic(pNode->GetFont()); + if (!bIsItalic) + AddAttribute(XML_NAMESPACE_MATH, XML_MATHVARIANT, XML_NORMAL); + pMath.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MI, true, false)); + } + else + { + // Export SmNodeType::MathIdent and SmNodeType::Place symbols as <mi> elements: + // - These math symbols should not be drawn slanted. Hence we should + // attach a mathvariant="normal" attribute to single-char <mi> elements + // that are not mathematical alphanumeric symbol. For simplicity and to + // work around browser limitations, we always attach such an attribute. + // - The MathML specification suggests to use empty <mi> elements as + // placeholders but they won't be visible in most MathML rendering + // engines so let's use an empty square for SmNodeType::Place instead. + AddAttribute(XML_NAMESPACE_MATH, XML_MATHVARIANT, XML_NORMAL); + pMath.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MI, true, false)); + } + auto nArse = ConvertMathToMathML(pTemp->GetText()); + OSL_ENSURE(nArse != 0xffff, "Non existent symbol"); + GetDocHandler()->characters(OUString(&nArse, 1)); +} + +void SmXMLExport::ExportText(const SmNode* pNode) +{ + std::unique_ptr<SvXMLElementExport> pText; + const SmTextNode* pTemp = static_cast<const SmTextNode*>(pNode); + switch (pNode->GetToken().eType) + { + default: + case TIDENT: + { + //Note that we change the fontstyle to italic for strings that + //are italic and longer than a single character. + bool bIsItalic = IsItalic(pTemp->GetFont()); + if ((pTemp->GetText().getLength() > 1) && bIsItalic) + AddAttribute(XML_NAMESPACE_MATH, XML_MATHVARIANT, XML_ITALIC); + else if ((pTemp->GetText().getLength() == 1) && !bIsItalic) + AddAttribute(XML_NAMESPACE_MATH, XML_MATHVARIANT, XML_NORMAL); + pText.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MI, true, false)); + break; + } + case TNUMBER: + pText.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MN, true, false)); + break; + case TTEXT: + pText.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MTEXT, true, false)); + break; + } + GetDocHandler()->characters(pTemp->GetText()); +} + +void SmXMLExport::ExportBlank(const SmNode* pNode) +{ + const SmBlankNode* pTemp = static_cast<const SmBlankNode*>(pNode); + //!! exports an <mspace> element. Note that for example "~_~" is allowed in + //!! Math (so it has no sense at all) but must not result in an empty + //!! <msub> tag in MathML !! + + if (pTemp->GetBlankNum() != 0) + { + // Attach a width attribute. We choose the (somewhat arbitrary) values + // ".5em" for a small gap '`' and "2em" for a large gap '~'. + // (see SmBlankNode::IncreaseBy for how pTemp->mnNum is set). + OUStringBuffer sStrBuf; + ::sax::Converter::convertDouble(sStrBuf, pTemp->GetBlankNum() * .5); + sStrBuf.append("em"); + AddAttribute(XML_NAMESPACE_MATH, XML_WIDTH, sStrBuf.makeStringAndClear()); + } + + SvXMLElementExport aTextExport(*this, XML_NAMESPACE_MATH, XML_MSPACE, true, false); + + GetDocHandler()->characters(OUString()); +} + +void SmXMLExport::ExportSubSupScript(const SmNode* pNode, int nLevel) +{ + const SmNode* pSub = nullptr; + const SmNode* pSup = nullptr; + const SmNode* pCSub = nullptr; + const SmNode* pCSup = nullptr; + const SmNode* pLSub = nullptr; + const SmNode* pLSup = nullptr; + std::unique_ptr<SvXMLElementExport> pThing2; + + //if we have prescripts at all then we must use the tensor notation + + //This is one of those excellent locations where scope is vital to + //arrange the construction and destruction of the element helper + //classes correctly + pLSub = pNode->GetSubNode(LSUB + 1); + pLSup = pNode->GetSubNode(LSUP + 1); + if (pLSub || pLSup) + { + SvXMLElementExport aMultiScripts(*this, XML_NAMESPACE_MATH, XML_MMULTISCRIPTS, true, true); + + if (nullptr != (pCSub = pNode->GetSubNode(CSUB + 1)) + && nullptr != (pCSup = pNode->GetSubNode(CSUP + 1))) + { + pThing2.reset( + new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MUNDEROVER, true, true)); + } + else if (nullptr != (pCSub = pNode->GetSubNode(CSUB + 1))) + { + pThing2.reset( + new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MUNDER, true, true)); + } + else if (nullptr != (pCSup = pNode->GetSubNode(CSUP + 1))) + { + pThing2.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MOVER, true, true)); + } + + ExportNodes(pNode->GetSubNode(0), nLevel + 1); //Main Term + + if (pCSub) + ExportNodes(pCSub, nLevel + 1); + if (pCSup) + ExportNodes(pCSup, nLevel + 1); + pThing2.reset(); + + pSub = pNode->GetSubNode(RSUB + 1); + pSup = pNode->GetSubNode(RSUP + 1); + if (pSub || pSup) + { + if (pSub) + ExportNodes(pSub, nLevel + 1); + else + { + SvXMLElementExport aNone(*this, XML_NAMESPACE_MATH, XML_NONE, true, true); + } + if (pSup) + ExportNodes(pSup, nLevel + 1); + else + { + SvXMLElementExport aNone(*this, XML_NAMESPACE_MATH, XML_NONE, true, true); + } + } + + //Separator element between suffix and prefix sub/sup pairs + { + SvXMLElementExport aPrescripts(*this, XML_NAMESPACE_MATH, XML_MPRESCRIPTS, true, true); + } + + if (pLSub) + ExportNodes(pLSub, nLevel + 1); + else + { + SvXMLElementExport aNone(*this, XML_NAMESPACE_MATH, XML_NONE, true, true); + } + if (pLSup) + ExportNodes(pLSup, nLevel + 1); + else + { + SvXMLElementExport aNone(*this, XML_NAMESPACE_MATH, XML_NONE, true, true); + } + } + else + { + std::unique_ptr<SvXMLElementExport> pThing; + if (nullptr != (pSub = pNode->GetSubNode(RSUB + 1)) + && nullptr != (pSup = pNode->GetSubNode(RSUP + 1))) + { + pThing.reset( + new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MSUBSUP, true, true)); + } + else if (nullptr != (pSub = pNode->GetSubNode(RSUB + 1))) + { + pThing.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MSUB, true, true)); + } + else if (nullptr != (pSup = pNode->GetSubNode(RSUP + 1))) + { + pThing.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MSUP, true, true)); + } + + if (nullptr != (pCSub = pNode->GetSubNode(CSUB + 1)) + && nullptr != (pCSup = pNode->GetSubNode(CSUP + 1))) + { + pThing2.reset( + new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MUNDEROVER, true, true)); + } + else if (nullptr != (pCSub = pNode->GetSubNode(CSUB + 1))) + { + pThing2.reset( + new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MUNDER, true, true)); + } + else if (nullptr != (pCSup = pNode->GetSubNode(CSUP + 1))) + { + pThing2.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MOVER, true, true)); + } + ExportNodes(pNode->GetSubNode(0), nLevel + 1); //Main Term + + if (pCSub) + ExportNodes(pCSub, nLevel + 1); + if (pCSup) + ExportNodes(pCSup, nLevel + 1); + pThing2.reset(); + + if (pSub) + ExportNodes(pSub, nLevel + 1); + if (pSup) + ExportNodes(pSup, nLevel + 1); + pThing.reset(); + } +} + +void SmXMLExport::ExportBrace(const SmNode* pNode, int nLevel) +{ + const SmNode* pTemp; + const SmNode* pLeft = pNode->GetSubNode(0); + const SmNode* pRight = pNode->GetSubNode(2); + + // This used to generate <mfenced> or <mrow>+<mo> elements according to + // the stretchiness of fences. The MathML recommendation defines an + // <mrow>+<mo> construction that is equivalent to the <mfenced> element: + // http://www.w3.org/TR/MathML3/chapter3.html#presm.mfenced + // To simplify our code and avoid issues with mfenced implementations in + // MathML rendering engines, we now always generate <mrow>+<mo> elements. + // See #fdo 66282. + + // <mrow> + SvXMLElementExport aRow(*this, XML_NAMESPACE_MATH, XML_MROW, true, true); + + // <mo fence="true"> opening-fence </mo> + if (pLeft && (pLeft->GetToken().eType != TNONE)) + { + AddAttribute(XML_NAMESPACE_MATH, XML_FENCE, XML_TRUE); + AddAttribute(XML_NAMESPACE_MATH, XML_FORM, XML_PREFIX); + if (pNode->GetScaleMode() == SmScaleMode::Height) + AddAttribute(XML_NAMESPACE_MATH, XML_STRETCHY, XML_TRUE); + else + AddAttribute(XML_NAMESPACE_MATH, XML_STRETCHY, XML_FALSE); + ExportNodes(pLeft, nLevel + 1); + } + + if (nullptr != (pTemp = pNode->GetSubNode(1))) + { + // <mrow> + SvXMLElementExport aRowExport(*this, XML_NAMESPACE_MATH, XML_MROW, true, true); + ExportNodes(pTemp, nLevel + 1); + // </mrow> + } + + // <mo fence="true"> closing-fence </mo> + if (pRight && (pRight->GetToken().eType != TNONE)) + { + AddAttribute(XML_NAMESPACE_MATH, XML_FENCE, XML_TRUE); + AddAttribute(XML_NAMESPACE_MATH, XML_FORM, XML_POSTFIX); + if (pNode->GetScaleMode() == SmScaleMode::Height) + AddAttribute(XML_NAMESPACE_MATH, XML_STRETCHY, XML_TRUE); + else + AddAttribute(XML_NAMESPACE_MATH, XML_STRETCHY, XML_FALSE); + ExportNodes(pRight, nLevel + 1); + } + + // </mrow> +} + +void SmXMLExport::ExportRoot(const SmNode* pNode, int nLevel) +{ + if (pNode->GetSubNode(0)) + { + SvXMLElementExport aRoot(*this, XML_NAMESPACE_MATH, XML_MROOT, true, true); + ExportNodes(pNode->GetSubNode(2), nLevel + 1); + ExportNodes(pNode->GetSubNode(0), nLevel + 1); + } + else + { + SvXMLElementExport aSqrt(*this, XML_NAMESPACE_MATH, XML_MSQRT, true, true); + ExportNodes(pNode->GetSubNode(2), nLevel + 1); + } +} + +void SmXMLExport::ExportOperator(const SmNode* pNode, int nLevel) +{ + /*we need to either use content or font and size attributes + *here*/ + SvXMLElementExport aRow(*this, XML_NAMESPACE_MATH, XML_MROW, true, true); + ExportNodes(pNode->GetSubNode(0), nLevel + 1); + ExportNodes(pNode->GetSubNode(1), nLevel + 1); +} + +void SmXMLExport::ExportAttributes(const SmNode* pNode, int nLevel) +{ + std::unique_ptr<SvXMLElementExport> pElement; + + if (pNode->GetToken().eType == TUNDERLINE) + { + AddAttribute(XML_NAMESPACE_MATH, XML_ACCENTUNDER, XML_TRUE); + pElement.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MUNDER, true, true)); + } + else if (pNode->GetToken().eType == TOVERSTRIKE) + { + // export as <menclose notation="horizontalstrike"> + AddAttribute(XML_NAMESPACE_MATH, XML_NOTATION, XML_HORIZONTALSTRIKE); + pElement.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MENCLOSE, true, true)); + } + else + { + AddAttribute(XML_NAMESPACE_MATH, XML_ACCENT, XML_TRUE); + pElement.reset(new SvXMLElementExport(*this, XML_NAMESPACE_MATH, XML_MOVER, true, true)); + } + + ExportNodes(pNode->GetSubNode(1), nLevel + 1); + switch (pNode->GetToken().eType) + { + case TOVERLINE: + { + //proper entity support required + SvXMLElementExport aMath(*this, XML_NAMESPACE_MATH, XML_MO, true, true); + static constexpr OUStringLiteral nArse = u"\u00AF"; + GetDocHandler()->characters(nArse); + } + break; + case TUNDERLINE: + { + //proper entity support required + SvXMLElementExport aMath(*this, XML_NAMESPACE_MATH, XML_MO, true, true); + static constexpr OUStringLiteral nArse = u"\u0332"; + GetDocHandler()->characters(nArse); + } + break; + case TOVERSTRIKE: + break; + case TWIDETILDE: + case TWIDEHAT: + case TWIDEVEC: + case TWIDEHARPOON: + { + // make these wide accents stretchy + AddAttribute(XML_NAMESPACE_MATH, XML_STRETCHY, XML_TRUE); + ExportNodes(pNode->GetSubNode(0), nLevel + 1); + } + break; + default: + ExportNodes(pNode->GetSubNode(0), nLevel + 1); + break; + } +} + +static bool lcl_HasEffectOnMathvariant(const SmTokenType eType) +{ + return eType == TBOLD || eType == TNBOLD || eType == TITALIC || eType == TNITALIC + || eType == TSANS || eType == TSERIF || eType == TFIXED; +} + +void SmXMLExport::ExportFont(const SmNode* pNode, int nLevel) +{ + // gather the mathvariant attribute relevant data from all + // successively following SmFontNodes... + + int nBold = -1; // for the following variables: -1 = yet undefined; 0 = false; 1 = true; + int nItalic = -1; // for the following variables: -1 = yet undefined; 0 = false; 1 = true; + int nSansSerifFixed = -1; + SmTokenType eNodeType = TUNKNOWN; + + for (;;) + { + eNodeType = pNode->GetToken().eType; + if (!lcl_HasEffectOnMathvariant(eNodeType)) + break; + switch (eNodeType) + { + case TBOLD: + nBold = 1; + break; + case TNBOLD: + nBold = 0; + break; + case TITALIC: + nItalic = 1; + break; + case TNITALIC: + nItalic = 0; + break; + case TSANS: + nSansSerifFixed = 0; + break; + case TSERIF: + nSansSerifFixed = 1; + break; + case TFIXED: + nSansSerifFixed = 2; + break; + default: + SAL_WARN("starmath", "unexpected case"); + } + // According to the parser every node that is to be evaluated here + // has a single non-zero subnode at index 1!! Thus we only need to check + // that single node for follow-up nodes that have an effect on the attribute. + if (pNode->GetNumSubNodes() > 1 && pNode->GetSubNode(1) + && lcl_HasEffectOnMathvariant(pNode->GetSubNode(1)->GetToken().eType)) + { + pNode = pNode->GetSubNode(1); + } + else + break; + } + + sal_uInt32 nc; + switch (pNode->GetToken().eType) + { + case TPHANTOM: + // No attribute needed. An <mphantom> element will be used below. + break; + case TMATHMLCOL: + { + nc = pNode->GetToken().cMathChar.toUInt32(16); + const OUString& sssStr = starmathdatabase::Identify_Color_MATHML(nc).aIdent; + AddAttribute(XML_NAMESPACE_MATH, XML_MATHCOLOR, sssStr); + } + break; + case TRGB: + case TRGBA: + case THEX: + case THTMLCOL: + case TDVIPSNAMESCOL: + case TICONICCOL: + { + nc = pNode->GetToken().cMathChar.toUInt32(16); + OUString ssStr("#" + Color(ColorTransparency, nc).AsRGBHEXString()); + AddAttribute(XML_NAMESPACE_MATH, XML_MATHCOLOR, ssStr); + } + break; + case TSIZE: + { + const SmFontNode* pFontNode = static_cast<const SmFontNode*>(pNode); + const Fraction& aFrac = pFontNode->GetSizeParameter(); + + OUStringBuffer sStrBuf; + switch (pFontNode->GetSizeType()) + { + case FontSizeType::MULTIPLY: + ::sax::Converter::convertDouble(sStrBuf, + static_cast<double>(aFrac * Fraction(100, 1))); + sStrBuf.append('%'); + break; + case FontSizeType::DIVIDE: + ::sax::Converter::convertDouble(sStrBuf, + static_cast<double>(Fraction(100, 1) / aFrac)); + sStrBuf.append('%'); + break; + case FontSizeType::ABSOLUT: + ::sax::Converter::convertDouble(sStrBuf, static_cast<double>(aFrac)); + sStrBuf.append(GetXMLToken(XML_UNIT_PT)); + break; + default: + { + //The problem here is that the wheels fall off because + //font size is stored in 100th's of a mm not pts, and + //rounding errors take their toll on the original + //value specified in points. + + //Must fix StarMath to retain the original pt values + double mytest + = o3tl::convert<double>(pFontNode->GetFont().GetFontSize().Height(), + SmO3tlLengthUnit(), o3tl::Length::pt); + + if (pFontNode->GetSizeType() == FontSizeType::MINUS) + mytest -= static_cast<double>(aFrac); + else + mytest += static_cast<double>(aFrac); + + mytest = ::rtl::math::round(mytest, 1); + ::sax::Converter::convertDouble(sStrBuf, mytest); + sStrBuf.append(GetXMLToken(XML_UNIT_PT)); + } + break; + } + + OUString sStr(sStrBuf.makeStringAndClear()); + AddAttribute(XML_NAMESPACE_MATH, XML_MATHSIZE, sStr); + } + break; + case TBOLD: + case TITALIC: + case TNBOLD: + case TNITALIC: + case TFIXED: + case TSANS: + case TSERIF: + { + // nBold: -1 = yet undefined; 0 = false; 1 = true; + // nItalic: -1 = yet undefined; 0 = false; 1 = true; + // nSansSerifFixed: -1 = undefined; 0 = sans; 1 = serif; 2 = fixed; + const char* pText = "normal"; + if (nSansSerifFixed == -1 || nSansSerifFixed == 1) + { + pText = "normal"; + if (nBold == 1 && nItalic != 1) + pText = "bold"; + else if (nBold != 1 && nItalic == 1) + pText = "italic"; + else if (nBold == 1 && nItalic == 1) + pText = "bold-italic"; + } + else if (nSansSerifFixed == 0) + { + pText = "sans-serif"; + if (nBold == 1 && nItalic != 1) + pText = "bold-sans-serif"; + else if (nBold != 1 && nItalic == 1) + pText = "sans-serif-italic"; + else if (nBold == 1 && nItalic == 1) + pText = "sans-serif-bold-italic"; + } + else if (nSansSerifFixed == 2) + pText = "monospace"; // no modifiers allowed for monospace ... + else + { + SAL_WARN("starmath", "unexpected case"); + } + AddAttribute(XML_NAMESPACE_MATH, XML_MATHVARIANT, OUString::createFromAscii(pText)); + } + break; + default: + break; + } + { + // Wrap everything in an <mphantom> or <mstyle> element. These elements + // are mrow-like, so ExportExpression doesn't need to add an explicit + // <mrow> element. See #fdo 66283. + SvXMLElementExport aElement(*this, XML_NAMESPACE_MATH, + pNode->GetToken().eType == TPHANTOM ? XML_MPHANTOM : XML_MSTYLE, + true, true); + ExportExpression(pNode, nLevel, true); + } +} + +void SmXMLExport::ExportVerticalBrace(const SmVerticalBraceNode* pNode, int nLevel) +{ + // "[body] overbrace [script]" + + // Position body, overbrace and script vertically. First place the overbrace + // OVER the body and then the script OVER this expression. + + // [script] + // --[overbrace]-- + // XXXXXX[body]XXXXXXX + + // Similarly for the underbrace construction. + + XMLTokenEnum which; + + switch (pNode->GetToken().eType) + { + case TOVERBRACE: + default: + which = XML_MOVER; + break; + case TUNDERBRACE: + which = XML_MUNDER; + break; + } + + SvXMLElementExport aOver1(*this, XML_NAMESPACE_MATH, which, true, true); + { //Scoping + // using accents will draw the over-/underbraces too close to the base + // see http://www.w3.org/TR/MathML2/chapter3.html#id.3.4.5.2 + // also XML_ACCENT is illegal with XML_MUNDER. Thus no XML_ACCENT attribute here! + SvXMLElementExport aOver2(*this, XML_NAMESPACE_MATH, which, true, true); + ExportNodes(pNode->Body(), nLevel); + AddAttribute(XML_NAMESPACE_MATH, XML_STRETCHY, XML_TRUE); + ExportNodes(pNode->Brace(), nLevel); + } + ExportNodes(pNode->Script(), nLevel); +} + +void SmXMLExport::ExportMatrix(const SmNode* pNode, int nLevel) +{ + SvXMLElementExport aTable(*this, XML_NAMESPACE_MATH, XML_MTABLE, true, true); + const SmMatrixNode* pMatrix = static_cast<const SmMatrixNode*>(pNode); + size_t i = 0; + for (sal_uInt16 y = 0; y < pMatrix->GetNumRows(); y++) + { + SvXMLElementExport aRow(*this, XML_NAMESPACE_MATH, XML_MTR, true, true); + for (sal_uInt16 x = 0; x < pMatrix->GetNumCols(); x++) + { + if (const SmNode* pTemp = pNode->GetSubNode(i++)) + { + if (pTemp->GetType() == SmNodeType::Align && pTemp->GetToken().eType != TALIGNC) + { + // A left or right alignment is specified on this cell, + // attach the corresponding columnalign attribute. + AddAttribute(XML_NAMESPACE_MATH, XML_COLUMNALIGN, + pTemp->GetToken().eType == TALIGNL ? XML_LEFT : XML_RIGHT); + } + SvXMLElementExport aCell(*this, XML_NAMESPACE_MATH, XML_MTD, true, true); + ExportNodes(pTemp, nLevel + 1); + } + } + } +} + +void SmXMLExport::ExportNodes(const SmNode* pNode, int nLevel) +{ + if (!pNode) + return; + switch (pNode->GetType()) + { + case SmNodeType::Table: + ExportTable(pNode, nLevel); + break; + case SmNodeType::Align: + case SmNodeType::Bracebody: + case SmNodeType::Expression: + ExportExpression(pNode, nLevel); + break; + case SmNodeType::Line: + ExportLine(pNode, nLevel); + break; + case SmNodeType::Text: + ExportText(pNode); + break; + case SmNodeType::GlyphSpecial: + case SmNodeType::Math: + { + const SmTextNode* pTemp = static_cast<const SmTextNode*>(pNode); + if (pTemp->GetText().isEmpty()) + { + // no conversion to MathML implemented -> export it as text + // thus at least it will not vanish into nothing + ExportText(pNode); + } + else + { + switch (pNode->GetToken().eType) + { + case TINTD: + AddAttribute(XML_NAMESPACE_MATH, XML_STRETCHY, XML_TRUE); + break; + default: + break; + } + //To fully handle generic MathML we need to implement the full + //operator dictionary, we will generate MathML with explicit + //stretchiness for now. + sal_Int16 nLength = GetAttrList().getLength(); + bool bAddStretch = true; + for (sal_Int16 i = 0; i < nLength; i++) + { + OUString sLocalName; + sal_uInt16 nPrefix = GetNamespaceMap().GetKeyByAttrName( + GetAttrList().getNameByIndex(i), &sLocalName); + + if ((XML_NAMESPACE_MATH == nPrefix) && IsXMLToken(sLocalName, XML_STRETCHY)) + { + bAddStretch = false; + break; + } + } + if (bAddStretch) + { + AddAttribute(XML_NAMESPACE_MATH, XML_STRETCHY, XML_FALSE); + } + ExportMath(pNode); + } + } + break; + case SmNodeType:: + Special: //SmNodeType::Special requires some sort of Entity preservation in the XML engine. + case SmNodeType::MathIdent: + case SmNodeType::Place: + ExportMath(pNode); + break; + case SmNodeType::BinHor: + ExportBinaryHorizontal(pNode, nLevel); + break; + case SmNodeType::UnHor: + ExportUnaryHorizontal(pNode, nLevel); + break; + case SmNodeType::Brace: + ExportBrace(pNode, nLevel); + break; + case SmNodeType::BinVer: + ExportBinaryVertical(pNode, nLevel); + break; + case SmNodeType::BinDiagonal: + ExportBinaryDiagonal(pNode, nLevel); + break; + case SmNodeType::SubSup: + ExportSubSupScript(pNode, nLevel); + break; + case SmNodeType::Root: + ExportRoot(pNode, nLevel); + break; + case SmNodeType::Oper: + ExportOperator(pNode, nLevel); + break; + case SmNodeType::Attribute: + ExportAttributes(pNode, nLevel); + break; + case SmNodeType::Font: + ExportFont(pNode, nLevel); + break; + case SmNodeType::VerticalBrace: + ExportVerticalBrace(static_cast<const SmVerticalBraceNode*>(pNode), nLevel); + break; + case SmNodeType::Matrix: + ExportMatrix(pNode, nLevel); + break; + case SmNodeType::Blank: + ExportBlank(pNode); + break; + default: + SAL_WARN("starmath", "Warning: failed to export a node?"); + break; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/mathml/mathmlimport.cxx b/starmath/source/mathml/mathmlimport.cxx new file mode 100644 index 0000000000..7bc3e5b913 --- /dev/null +++ b/starmath/source/mathml/mathmlimport.cxx @@ -0,0 +1,2673 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +/*todo: Change characters and tcharacters to accumulate the characters together +into one string, xml parser hands them to us line by line rather than all in +one go*/ + +#include <com/sun/star/xml/sax/InputSource.hpp> +#include <com/sun/star/xml/sax/FastParser.hpp> +#include <com/sun/star/xml/sax/Parser.hpp> +#include <com/sun/star/xml/sax/SAXParseException.hpp> +#include <com/sun/star/document/XDocumentPropertiesSupplier.hpp> +#include <com/sun/star/packages/WrongPasswordException.hpp> +#include <com/sun/star/packages/zip/ZipIOException.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/embed/ElementModes.hpp> +#include <com/sun/star/uno/Any.h> +#include <com/sun/star/task/XStatusIndicator.hpp> + +#include <comphelper/fileformat.h> +#include <comphelper/genericpropertyset.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/servicehelper.hxx> +#include <comphelper/propertysetinfo.hxx> +#include <rtl/character.hxx> +#include <sal/log.hxx> +#include <sfx2/frame.hxx> +#include <sfx2/docfile.hxx> +#include <sfx2/sfxsids.hrc> +#include <sfx2/sfxmodelfactory.hxx> +#include <osl/diagnose.h> +#include <sot/storage.hxx> +#include <svtools/sfxecode.hxx> +#include <svl/itemset.hxx> +#include <svl/stritem.hxx> +#include <unotools/streamwrap.hxx> +#include <sax/tools/converter.hxx> +#include <xmloff/DocumentSettingsContext.hxx> +#include <xmloff/xmlnamespace.hxx> +#include <xmloff/xmltoken.hxx> +#include <xmloff/xmluconv.hxx> +#include <xmloff/xmlmetai.hxx> +#include <svx/dialmgr.hxx> +#include <svx/strings.hrc> +#include <comphelper/diagnose_ex.hxx> +#include <o3tl/string_view.hxx> + +#include <mathmlattr.hxx> +#include <xparsmlbase.hxx> +#include <mathmlimport.hxx> +#include <document.hxx> +#include <smdll.hxx> +#include <unomodel.hxx> +#include <utility.hxx> +#include <visitors.hxx> +#include <starmathdatabase.hxx> +#include <smmod.hxx> +#include <cfgitem.hxx> + +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::document; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star; +using namespace ::xmloff::token; + +namespace +{ +std::unique_ptr<SmNode> popOrZero(SmNodeStack& rStack) +{ + if (rStack.empty()) + return nullptr; + auto pTmp = std::move(rStack.front()); + rStack.pop_front(); + return pTmp; +} +} + +ErrCode SmXMLImportWrapper::Import(SfxMedium& rMedium) +{ + ErrCode nError = ERRCODE_SFX_DOLOADFAILED; + + uno::Reference<uno::XComponentContext> xContext(comphelper::getProcessComponentContext()); + + OSL_ENSURE(m_xModel.is(), "XMLReader::Read: got no model"); + + // try to get an XStatusIndicator from the Medium + uno::Reference<task::XStatusIndicator> xStatusIndicator; + + bool bEmbedded = false; + SmModel* pModel = m_xModel.get(); + + SmDocShell* pDocShell = pModel ? static_cast<SmDocShell*>(pModel->GetObjectShell()) : nullptr; + if (pDocShell) + { + OSL_ENSURE(pDocShell->GetMedium() == &rMedium, "different SfxMedium found"); + + const SfxUnoAnyItem* pItem = rMedium.GetItemSet().GetItem(SID_PROGRESS_STATUSBAR_CONTROL); + if (pItem) + pItem->GetValue() >>= xStatusIndicator; + + if (SfxObjectCreateMode::EMBEDDED == pDocShell->GetCreateMode()) + bEmbedded = true; + } + + static const comphelper::PropertyMapEntry aInfoMap[] + = { { OUString("PrivateData"), 0, cppu::UnoType<XInterface>::get(), + beans::PropertyAttribute::MAYBEVOID, 0 }, + { OUString("BaseURI"), 0, ::cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::MAYBEVOID, 0 }, + { OUString("StreamRelPath"), 0, ::cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::MAYBEVOID, 0 }, + { OUString("StreamName"), 0, ::cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::MAYBEVOID, 0 } }; + uno::Reference<beans::XPropertySet> xInfoSet( + comphelper::GenericPropertySet_CreateInstance(new comphelper::PropertySetInfo(aInfoMap))); + + // Set base URI + OUString const baseURI(rMedium.GetBaseURL()); + // needed for relative URLs; but it's OK to import e.g. MathML from the + // clipboard without one + SAL_INFO_IF(baseURI.isEmpty(), "starmath", "SmXMLImportWrapper: no base URL"); + xInfoSet->setPropertyValue("BaseURI", Any(baseURI)); + + sal_Int32 nSteps = 3; + if (!(rMedium.IsStorage())) + nSteps = 1; + + sal_Int32 nProgressRange(nSteps); + if (xStatusIndicator.is()) + { + xStatusIndicator->start(SvxResId(RID_SVXSTR_DOC_LOAD), nProgressRange); + } + + nSteps = 0; + if (xStatusIndicator.is()) + xStatusIndicator->setValue(nSteps++); + + if (rMedium.IsStorage()) + { + // TODO/LATER: handle the case of embedded links gracefully + if (bEmbedded) // && !rMedium.GetStorage()->IsRoot() ) + { + OUString aName("dummyObjName"); + const SfxStringItem* pDocHierarchItem + = rMedium.GetItemSet().GetItem(SID_DOC_HIERARCHICALNAME); + if (pDocHierarchItem) + aName = pDocHierarchItem->GetValue(); + + if (!aName.isEmpty()) + { + xInfoSet->setPropertyValue("StreamRelPath", Any(aName)); + } + } + + bool bOASIS = (SotStorage::GetVersion(rMedium.GetStorage()) > SOFFICE_FILEFORMAT_60); + if (xStatusIndicator.is()) + xStatusIndicator->setValue(nSteps++); + + auto nWarn + = ReadThroughComponent(rMedium.GetStorage(), m_xModel, "meta.xml", xContext, xInfoSet, + (bOASIS ? "com.sun.star.comp.Math.XMLOasisMetaImporter" + : "com.sun.star.comp.Math.XMLMetaImporter"), + m_bUseHTMLMLEntities); + + if (nWarn != ERRCODE_IO_BROKENPACKAGE) + { + if (xStatusIndicator.is()) + xStatusIndicator->setValue(nSteps++); + + nWarn = ReadThroughComponent(rMedium.GetStorage(), m_xModel, "settings.xml", xContext, + xInfoSet, + (bOASIS ? "com.sun.star.comp.Math.XMLOasisSettingsImporter" + : "com.sun.star.comp.Math.XMLSettingsImporter"), + m_bUseHTMLMLEntities); + + if (nWarn != ERRCODE_IO_BROKENPACKAGE) + { + if (xStatusIndicator.is()) + xStatusIndicator->setValue(nSteps++); + + nError = ReadThroughComponent( + rMedium.GetStorage(), m_xModel, "content.xml", xContext, xInfoSet, + "com.sun.star.comp.Math.XMLImporter", m_bUseHTMLMLEntities); + } + else + nError = ERRCODE_IO_BROKENPACKAGE; + } + else + nError = ERRCODE_IO_BROKENPACKAGE; + } + else + { + Reference<io::XInputStream> xInputStream + = new utl::OInputStreamWrapper(rMedium.GetInStream()); + + if (xStatusIndicator.is()) + xStatusIndicator->setValue(nSteps++); + + nError = ReadThroughComponent(xInputStream, m_xModel, xContext, xInfoSet, + "com.sun.star.comp.Math.XMLImporter", false, + m_bUseHTMLMLEntities); + } + + if (xStatusIndicator.is()) + xStatusIndicator->end(); + return nError; +} + +/// read a component (file + filter version) +ErrCode SmXMLImportWrapper::ReadThroughComponent(const Reference<io::XInputStream>& xInputStream, + const Reference<XComponent>& xModelComponent, + Reference<uno::XComponentContext> const& rxContext, + Reference<beans::XPropertySet> const& rPropSet, + const char* pFilterName, bool bEncrypted, + bool bUseHTMLMLEntities) +{ + ErrCode nError = ERRCODE_SFX_DOLOADFAILED; + OSL_ENSURE(xInputStream.is(), "input stream missing"); + OSL_ENSURE(xModelComponent.is(), "document missing"); + OSL_ENSURE(rxContext.is(), "factory missing"); + OSL_ENSURE(nullptr != pFilterName, "I need a service name for the component!"); + + // prepare ParserInputSource + xml::sax::InputSource aParserInput; + aParserInput.aInputStream = xInputStream; + + Sequence<Any> aArgs{ Any(rPropSet) }; + + // get filter + Reference<XInterface> xFilter + = rxContext->getServiceManager()->createInstanceWithArgumentsAndContext( + OUString::createFromAscii(pFilterName), aArgs, rxContext); + SAL_WARN_IF(!xFilter, "starmath", "Can't instantiate filter component " << pFilterName); + if (!xFilter.is()) + return nError; + + // connect model and filter + Reference<XImporter> xImporter(xFilter, UNO_QUERY); + xImporter->setTargetDocument(xModelComponent); + + // finally, parser the stream + try + { + Reference<css::xml::sax::XFastParser> xFastParser(xFilter, UNO_QUERY); + Reference<css::xml::sax::XFastDocumentHandler> xFastDocHandler(xFilter, UNO_QUERY); + if (xFastParser) + { + if (bUseHTMLMLEntities) + xFastParser->setCustomEntityNames(starmathdatabase::icustomMathmlHtmlEntities); + xFastParser->parseStream(aParserInput); + } + else if (xFastDocHandler) + { + Reference<css::xml::sax::XFastParser> xParser + = css::xml::sax::FastParser::create(rxContext); + if (bUseHTMLMLEntities) + xParser->setCustomEntityNames(starmathdatabase::icustomMathmlHtmlEntities); + xParser->setFastDocumentHandler(xFastDocHandler); + xParser->parseStream(aParserInput); + } + else + { + Reference<css::xml::sax::XDocumentHandler> xDocHandler(xFilter, UNO_QUERY); + assert(xDocHandler); + Reference<css::xml::sax::XParser> xParser = css::xml::sax::Parser::create(rxContext); + xParser->setDocumentHandler(xDocHandler); + xParser->parseStream(aParserInput); + } + + auto pFilter = dynamic_cast<SmXMLImport*>(xFilter.get()); + if (pFilter && pFilter->GetSuccess()) + nError = ERRCODE_NONE; + } + catch (const xml::sax::SAXParseException& r) + { + // sax parser sends wrapped exceptions, + // try to find the original one + xml::sax::SAXException aSaxEx = *static_cast<const xml::sax::SAXException*>(&r); + bool bTryChild = true; + + while (bTryChild) + { + xml::sax::SAXException aTmp; + if (aSaxEx.WrappedException >>= aTmp) + aSaxEx = aTmp; + else + bTryChild = false; + } + + packages::zip::ZipIOException aBrokenPackage; + if (aSaxEx.WrappedException >>= aBrokenPackage) + return ERRCODE_IO_BROKENPACKAGE; + + if (bEncrypted) + nError = ERRCODE_SFX_WRONGPASSWORD; + } + catch (const xml::sax::SAXException& r) + { + packages::zip::ZipIOException aBrokenPackage; + if (r.WrappedException >>= aBrokenPackage) + return ERRCODE_IO_BROKENPACKAGE; + + if (bEncrypted) + nError = ERRCODE_SFX_WRONGPASSWORD; + } + catch (const packages::zip::ZipIOException&) + { + nError = ERRCODE_IO_BROKENPACKAGE; + } + catch (const io::IOException&) + { + } + catch (const std::range_error&) + { + } + + return nError; +} + +ErrCode SmXMLImportWrapper::ReadThroughComponent(const uno::Reference<embed::XStorage>& xStorage, + const Reference<XComponent>& xModelComponent, + const char* pStreamName, + Reference<uno::XComponentContext> const& rxContext, + Reference<beans::XPropertySet> const& rPropSet, + const char* pFilterName, bool bUseHTMLMLEntities) +{ + OSL_ENSURE(xStorage.is(), "Need storage!"); + OSL_ENSURE(nullptr != pStreamName, "Please, please, give me a name!"); + + // open stream (and set parser input) + OUString sStreamName = OUString::createFromAscii(pStreamName); + + // get input stream + try + { + uno::Reference<io::XStream> xEventsStream + = xStorage->openStreamElement(sStreamName, embed::ElementModes::READ); + + // determine if stream is encrypted or not + uno::Reference<beans::XPropertySet> xProps(xEventsStream, uno::UNO_QUERY); + Any aAny = xProps->getPropertyValue("Encrypted"); + bool bEncrypted = false; + if (aAny.getValueType() == cppu::UnoType<bool>::get()) + aAny >>= bEncrypted; + + // set Base URL + if (rPropSet.is()) + { + rPropSet->setPropertyValue("StreamName", Any(sStreamName)); + } + + Reference<io::XInputStream> xStream = xEventsStream->getInputStream(); + return ReadThroughComponent(xStream, xModelComponent, rxContext, rPropSet, pFilterName, + bEncrypted, bUseHTMLMLEntities); + } + catch (packages::WrongPasswordException&) + { + return ERRCODE_SFX_WRONGPASSWORD; + } + catch (packages::zip::ZipIOException&) + { + return ERRCODE_IO_BROKENPACKAGE; + } + catch (uno::Exception&) + { + } + + return ERRCODE_SFX_DOLOADFAILED; +} + +SmXMLImport::SmXMLImport(const css::uno::Reference<css::uno::XComponentContext>& rContext, + OUString const& implementationName, SvXMLImportFlags nImportFlags) + : SvXMLImport(rContext, implementationName, nImportFlags) + , bSuccess(false) + , nParseDepth(0) + , mnSmSyntaxVersion(SM_MOD()->GetConfig()->GetDefaultSmSyntaxVersion()) +{ +} + +extern "C" SAL_DLLPUBLIC_EXPORT uno::XInterface* +Math_XMLImporter_get_implementation(uno::XComponentContext* pCtx, + uno::Sequence<uno::Any> const& /*rSeq*/) +{ + return cppu::acquire( + new SmXMLImport(pCtx, "com.sun.star.comp.Math.XMLImporter", SvXMLImportFlags::ALL)); +} + +extern "C" SAL_DLLPUBLIC_EXPORT uno::XInterface* +Math_XMLOasisMetaImporter_get_implementation(uno::XComponentContext* pCtx, + uno::Sequence<uno::Any> const& /*rSeq*/) +{ + return cppu::acquire(new SmXMLImport(pCtx, "com.sun.star.comp.Math.XMLOasisMetaImporter", + SvXMLImportFlags::META)); +} + +extern "C" SAL_DLLPUBLIC_EXPORT uno::XInterface* +Math_XMLOasisSettingsImporter_get_implementation(uno::XComponentContext* pCtx, + uno::Sequence<uno::Any> const& /*rSeq*/) +{ + return cppu::acquire(new SmXMLImport(pCtx, "com.sun.star.comp.Math.XMLOasisSettingsImporter", + SvXMLImportFlags::SETTINGS)); +} + +void SmXMLImport::endDocument() +{ + //Set the resulted tree into the SmDocShell where it belongs + std::unique_ptr<SmNode> pTree = popOrZero(aNodeStack); + if (pTree && pTree->GetType() == SmNodeType::Table) + { + uno::Reference<frame::XModel> xModel = GetModel(); + SmModel* pModel = dynamic_cast<SmModel*>(xModel.get()); + + if (pModel) + { + SmDocShell* pDocShell = static_cast<SmDocShell*>(pModel->GetObjectShell()); + auto pTreeTmp = pTree.get(); + pDocShell->SetFormulaTree(static_cast<SmTableNode*>(pTree.release())); + if (aText.isEmpty()) //If we picked up no annotation text + { + // Get text from imported formula + SmNodeToTextVisitor tmpvisitor(pTreeTmp, aText); + } + + // Convert symbol names + AbstractSmParser* rParser = pDocShell->GetParser(); + bool bVal = rParser->IsImportSymbolNames(); + rParser->SetImportSymbolNames(true); + auto pTmpTree = rParser->Parse(aText); + aText = rParser->GetText(); + pTmpTree.reset(); + rParser->SetImportSymbolNames(bVal); + + pDocShell->SetText(aText); + pDocShell->SetSmSyntaxVersion(mnSmSyntaxVersion); + } + OSL_ENSURE(pModel, "So there *was* a UNO problem after all"); + + bSuccess = true; + } + + SvXMLImport::endDocument(); +} + +namespace +{ +class SmXMLImportContext : public SvXMLImportContext +{ +public: + SmXMLImportContext(SmXMLImport& rImport) + : SvXMLImportContext(rImport) + { + GetSmImport().IncParseDepth(); + } + + virtual ~SmXMLImportContext() override { GetSmImport().DecParseDepth(); } + + SmXMLImport& GetSmImport() { return static_cast<SmXMLImport&>(GetImport()); } + + virtual void TCharacters(const OUString& /*rChars*/); + virtual void SAL_CALL characters(const OUString& rChars) override; + virtual void SAL_CALL startFastElement( + sal_Int32 /*nElement*/, + const css::uno::Reference<css::xml::sax::XFastAttributeList>& /*rAttrList*/) override + { + if (GetSmImport().TooDeep()) + throw std::range_error("too deep"); + } +}; +} + +void SmXMLImportContext::TCharacters(const OUString& /*rChars*/) {} + +void SmXMLImportContext::characters(const OUString& rChars) +{ + /* + Whitespace occurring within the content of token elements is "trimmed" + from the ends (i.e. all whitespace at the beginning and end of the + content is removed), and "collapsed" internally (i.e. each sequence of + 1 or more whitespace characters is replaced with one blank character). + */ + //collapsing not done yet! + const OUString& rChars2 = rChars.trim(); + if (!rChars2.isEmpty()) + TCharacters(rChars2 /*.collapse()*/); +} + +namespace +{ +struct SmXMLContext_Helper +{ + sal_Int8 nIsBold; + sal_Int8 nIsItalic; + double nFontSize; + OUString sFontFamily; + OUString sColor; + + SmXMLImportContext& rContext; + + explicit SmXMLContext_Helper(SmXMLImportContext& rImport) + : nIsBold(-1) + , nIsItalic(-1) + , nFontSize(0.0) + , rContext(rImport) + { + } + + bool IsFontNodeNeeded() const; + void RetrieveAttrs(const uno::Reference<xml::sax::XFastAttributeList>& xAttrList); + void ApplyAttrs(); +}; +} + +bool SmXMLContext_Helper::IsFontNodeNeeded() const +{ + return nIsBold != -1 || nIsItalic != -1 || nFontSize != 0.0 || !sFontFamily.isEmpty() + || !sColor.isEmpty(); +} + +void SmXMLContext_Helper::RetrieveAttrs( + const uno::Reference<xml::sax::XFastAttributeList>& xAttrList) +{ + bool bMvFound = false; + for (auto& aIter : sax_fastparser::castToFastAttributeList(xAttrList)) + { + // sometimes they have namespace, sometimes not? + switch (aIter.getToken() & TOKEN_MASK) + { + case XML_FONTWEIGHT: + nIsBold = sal_Int8(IsXMLToken(aIter, XML_BOLD)); + break; + case XML_FONTSTYLE: + nIsItalic = sal_Int8(IsXMLToken(aIter, XML_ITALIC)); + break; + case XML_FONTSIZE: + case XML_MATHSIZE: + { + OUString sValue = aIter.toString(); + ::sax::Converter::convertDouble(nFontSize, sValue); + rContext.GetSmImport().GetMM100UnitConverter().SetXMLMeasureUnit( + util::MeasureUnit::POINT); + if (-1 == sValue.indexOf(GetXMLToken(XML_UNIT_PT))) + { + if (-1 == sValue.indexOf('%')) + nFontSize = 0.0; + else + { + rContext.GetSmImport().GetMM100UnitConverter().SetXMLMeasureUnit( + util::MeasureUnit::PERCENT); + } + } + break; + } + case XML_FONTFAMILY: + sFontFamily = aIter.toString(); + break; + case XML_COLOR: + sColor = aIter.toString(); + break; + case XML_MATHCOLOR: + sColor = aIter.toString(); + break; + case XML_MATHVARIANT: + bMvFound = true; + break; + default: + XMLOFF_WARN_UNKNOWN("starmath", aIter); + break; + } + } + + if (bMvFound) + { + // Ignore deprecated attributes fontfamily, fontweight, and fontstyle + // in favor of mathvariant, as specified in + // <https://www.w3.org/TR/MathML3/chapter3.html#presm.deprecatt>. + sFontFamily.clear(); + nIsBold = -1; + nIsItalic = -1; + } +} + +void SmXMLContext_Helper::ApplyAttrs() +{ + SmNodeStack& rNodeStack = rContext.GetSmImport().GetNodeStack(); + + if (!IsFontNodeNeeded()) + return; + + SmToken aToken; + aToken.cMathChar = u""_ustr; + aToken.nLevel = 5; + + if (nIsBold != -1) + { + if (nIsBold) + aToken.eType = TBOLD; + else + aToken.eType = TNBOLD; + std::unique_ptr<SmFontNode> pFontNode(new SmFontNode(aToken)); + pFontNode->SetSubNodes(nullptr, popOrZero(rNodeStack)); + rNodeStack.push_front(std::move(pFontNode)); + } + if (nIsItalic != -1) + { + if (nIsItalic) + aToken.eType = TITALIC; + else + aToken.eType = TNITALIC; + std::unique_ptr<SmFontNode> pFontNode(new SmFontNode(aToken)); + pFontNode->SetSubNodes(nullptr, popOrZero(rNodeStack)); + rNodeStack.push_front(std::move(pFontNode)); + } + if (nFontSize != 0.0) + { + aToken.eType = TSIZE; + std::unique_ptr<SmFontNode> pFontNode(new SmFontNode(aToken)); + + if (util::MeasureUnit::PERCENT + == rContext.GetSmImport().GetMM100UnitConverter().GetXMLMeasureUnit()) + { + if (nFontSize < 100.00) + pFontNode->SetSizeParameter(Fraction(100.00 / nFontSize), FontSizeType::DIVIDE); + else + pFontNode->SetSizeParameter(Fraction(nFontSize / 100.00), FontSizeType::MULTIPLY); + } + else + pFontNode->SetSizeParameter(Fraction(nFontSize), FontSizeType::ABSOLUT); + + pFontNode->SetSubNodes(nullptr, popOrZero(rNodeStack)); + rNodeStack.push_front(std::move(pFontNode)); + } + if (!sColor.isEmpty()) + { + SmColorTokenTableEntry aSmColorTokenTableEntry; + aSmColorTokenTableEntry = starmathdatabase::Identify_ColorName_HTML(sColor); + if (aSmColorTokenTableEntry.eType == TRGB) + aSmColorTokenTableEntry = starmathdatabase::Identify_Color_Parser( + sal_uInt32(aSmColorTokenTableEntry.cColor)); + if (aSmColorTokenTableEntry.eType != TERROR) + { + aToken = aSmColorTokenTableEntry; + std::unique_ptr<SmFontNode> pFontNode(new SmFontNode(aToken)); + pFontNode->SetSubNodes(nullptr, popOrZero(rNodeStack)); + rNodeStack.push_front(std::move(pFontNode)); + } + // If not known, not implemented yet. Giving up. + } + if (sFontFamily.isEmpty()) + return; + + if (sFontFamily.equalsIgnoreAsciiCase(GetXMLToken(XML_FIXED))) + aToken.eType = TFIXED; + else if (sFontFamily.equalsIgnoreAsciiCase("sans")) + aToken.eType = TSANS; + else if (sFontFamily.equalsIgnoreAsciiCase("serif")) + aToken.eType = TSERIF; + else //Just give up, we need to extend our font mechanism to be + //more general + return; + + aToken.aText = sFontFamily; + std::unique_ptr<SmFontNode> pFontNode(new SmFontNode(aToken)); + pFontNode->SetSubNodes(nullptr, popOrZero(rNodeStack)); + rNodeStack.push_front(std::move(pFontNode)); +} + +namespace +{ +class SmXMLTokenAttrHelper +{ + SmXMLImportContext& mrContext; + MathMLMathvariantValue meMv; + bool mbMvFound; + +public: + SmXMLTokenAttrHelper(SmXMLImportContext& rContext) + : mrContext(rContext) + , meMv(MathMLMathvariantValue::Normal) + , mbMvFound(false) + { + } + + void RetrieveAttrs(const uno::Reference<xml::sax::XFastAttributeList>& xAttrList); + void ApplyAttrs(MathMLMathvariantValue eDefaultMv); +}; +} + +void SmXMLTokenAttrHelper::RetrieveAttrs( + const uno::Reference<xml::sax::XFastAttributeList>& xAttrList) +{ + for (auto& aIter : sax_fastparser::castToFastAttributeList(xAttrList)) + { + OUString sValue = aIter.toString(); + switch (aIter.getToken()) + { + case XML_MATHVARIANT: + if (!GetMathMLMathvariantValue(sValue, meMv)) + SAL_WARN("starmath", "failed to recognize mathvariant: " << sValue); + mbMvFound = true; + break; + default: + XMLOFF_WARN_UNKNOWN("starmath", aIter); + break; + } + } +} + +void SmXMLTokenAttrHelper::ApplyAttrs(MathMLMathvariantValue eDefaultMv) +{ + assert(eDefaultMv == MathMLMathvariantValue::Normal + || eDefaultMv == MathMLMathvariantValue::Italic); + + std::vector<SmTokenType> vVariant; + MathMLMathvariantValue eMv = mbMvFound ? meMv : eDefaultMv; + switch (eMv) + { + case MathMLMathvariantValue::Normal: + vVariant.push_back(TNITALIC); + break; + case MathMLMathvariantValue::Bold: + vVariant.push_back(TBOLD); + break; + case MathMLMathvariantValue::Italic: + // nothing to do + break; + case MathMLMathvariantValue::BoldItalic: + vVariant.push_back(TITALIC); + vVariant.push_back(TBOLD); + break; + case MathMLMathvariantValue::DoubleStruck: + // TODO + break; + case MathMLMathvariantValue::BoldFraktur: + // TODO: Fraktur + vVariant.push_back(TBOLD); + break; + case MathMLMathvariantValue::Script: + // TODO + break; + case MathMLMathvariantValue::BoldScript: + // TODO: Script + vVariant.push_back(TBOLD); + break; + case MathMLMathvariantValue::Fraktur: + // TODO + break; + case MathMLMathvariantValue::SansSerif: + vVariant.push_back(TSANS); + break; + case MathMLMathvariantValue::BoldSansSerif: + vVariant.push_back(TSANS); + vVariant.push_back(TBOLD); + break; + case MathMLMathvariantValue::SansSerifItalic: + vVariant.push_back(TITALIC); + vVariant.push_back(TSANS); + break; + case MathMLMathvariantValue::SansSerifBoldItalic: + vVariant.push_back(TITALIC); + vVariant.push_back(TBOLD); + vVariant.push_back(TSANS); + break; + case MathMLMathvariantValue::Monospace: + vVariant.push_back(TFIXED); + break; + case MathMLMathvariantValue::Initial: + case MathMLMathvariantValue::Tailed: + case MathMLMathvariantValue::Looped: + case MathMLMathvariantValue::Stretched: + // TODO + break; + } + if (vVariant.empty()) + return; + SmNodeStack& rNodeStack = mrContext.GetSmImport().GetNodeStack(); + for (auto eType : vVariant) + { + SmToken aToken; + aToken.eType = eType; + aToken.cMathChar = u""_ustr; + aToken.nLevel = 5; + std::unique_ptr<SmFontNode> pFontNode(new SmFontNode(aToken)); + pFontNode->SetSubNodes(nullptr, popOrZero(rNodeStack)); + rNodeStack.push_front(std::move(pFontNode)); + } +} + +namespace +{ +class SmXMLDocContext_Impl : public SmXMLImportContext +{ +public: + SmXMLDocContext_Impl(SmXMLImport& rImport) + : SmXMLImportContext(rImport) + { + } + + virtual uno::Reference<xml::sax::XFastContextHandler> SAL_CALL createFastChildContext( + sal_Int32 nElement, const uno::Reference<xml::sax::XFastAttributeList>& xAttrList) override; + + void SAL_CALL endFastElement(sal_Int32 nElement) override; +}; + +/*avert the gaze from the originator*/ +class SmXMLRowContext_Impl : public SmXMLDocContext_Impl +{ +protected: + size_t nElementCount; + +public: + SmXMLRowContext_Impl(SmXMLImport& rImport) + : SmXMLDocContext_Impl(rImport) + , nElementCount(GetSmImport().GetNodeStack().size()) + { + } + + virtual uno::Reference<xml::sax::XFastContextHandler> SAL_CALL createFastChildContext( + sal_Int32 nElement, const uno::Reference<xml::sax::XFastAttributeList>& xAttrList) override; + + uno::Reference<xml::sax::XFastContextHandler> StrictCreateChildContext(sal_Int32 nElement); + + virtual void SAL_CALL endFastElement(sal_Int32 nElement) override; +}; + +class SmXMLEncloseContext_Impl : public SmXMLRowContext_Impl +{ +public: + // TODO/LATER: convert <menclose notation="horizontalstrike"> into + // "overstrike{}" and extend the Math syntax to support more notations + SmXMLEncloseContext_Impl(SmXMLImport& rImport) + : SmXMLRowContext_Impl(rImport) + { + } + + void SAL_CALL endFastElement(sal_Int32 nElement) override; +}; +} + +void SmXMLEncloseContext_Impl::endFastElement(sal_Int32 nElement) +{ + /* + <menclose> accepts any number of arguments; if this number is not 1, its + contents are treated as a single "inferred <mrow>" containing its + arguments + */ + if (GetSmImport().GetNodeStack().size() - nElementCount != 1) + SmXMLRowContext_Impl::endFastElement(nElement); +} + +namespace +{ +class SmXMLFracContext_Impl : public SmXMLRowContext_Impl +{ +public: + // TODO/LATER: convert <mfrac bevelled="true"> into "wideslash{}{}" + SmXMLFracContext_Impl(SmXMLImport& rImport) + : SmXMLRowContext_Impl(rImport) + { + } + + void SAL_CALL endFastElement(sal_Int32 nElement) override; +}; + +class SmXMLSqrtContext_Impl : public SmXMLRowContext_Impl +{ +public: + SmXMLSqrtContext_Impl(SmXMLImport& rImport) + : SmXMLRowContext_Impl(rImport) + { + } + + void SAL_CALL endFastElement(sal_Int32 nElement) override; +}; + +class SmXMLRootContext_Impl : public SmXMLRowContext_Impl +{ +public: + SmXMLRootContext_Impl(SmXMLImport& rImport) + : SmXMLRowContext_Impl(rImport) + { + } + + void SAL_CALL endFastElement(sal_Int32 nElement) override; +}; + +class SmXMLStyleContext_Impl : public SmXMLRowContext_Impl +{ +protected: + SmXMLContext_Helper aStyleHelper; + +public: + /*Right now the style tag is completely ignored*/ + SmXMLStyleContext_Impl(SmXMLImport& rImport) + : SmXMLRowContext_Impl(rImport) + , aStyleHelper(*this) + { + } + + void SAL_CALL endFastElement(sal_Int32 nElement) override; + void SAL_CALL startFastElement( + sal_Int32 nElement, const uno::Reference<xml::sax::XFastAttributeList>& xAttrList) override; +}; +} + +void SmXMLStyleContext_Impl::startFastElement( + sal_Int32 /*nElement*/, const uno::Reference<xml::sax::XFastAttributeList>& xAttrList) +{ + aStyleHelper.RetrieveAttrs(xAttrList); +} + +void SmXMLStyleContext_Impl::endFastElement(sal_Int32 nElement) +{ + /* + <mstyle> accepts any number of arguments; if this number is not 1, its + contents are treated as a single "inferred <mrow>" containing its + arguments + */ + SmNodeStack& rNodeStack = GetSmImport().GetNodeStack(); + if (rNodeStack.size() - nElementCount != 1) + SmXMLRowContext_Impl::endFastElement(nElement); + aStyleHelper.ApplyAttrs(); +} + +namespace +{ +class SmXMLPaddedContext_Impl : public SmXMLRowContext_Impl +{ +public: + /*Right now the style tag is completely ignored*/ + SmXMLPaddedContext_Impl(SmXMLImport& rImport) + : SmXMLRowContext_Impl(rImport) + { + } + + void SAL_CALL endFastElement(sal_Int32 nElement) override; +}; +} + +void SmXMLPaddedContext_Impl::endFastElement(sal_Int32 nElement) +{ + /* + <mpadded> accepts any number of arguments; if this number is not 1, its + contents are treated as a single "inferred <mrow>" containing its + arguments + */ + if (GetSmImport().GetNodeStack().size() - nElementCount != 1) + SmXMLRowContext_Impl::endFastElement(nElement); +} + +namespace +{ +class SmXMLPhantomContext_Impl : public SmXMLRowContext_Impl +{ +public: + /*Right now the style tag is completely ignored*/ + SmXMLPhantomContext_Impl(SmXMLImport& rImport) + : SmXMLRowContext_Impl(rImport) + { + } + + void SAL_CALL endFastElement(sal_Int32 nElement) override; +}; +} + +void SmXMLPhantomContext_Impl::endFastElement(sal_Int32 nElement) +{ + /* + <mphantom> accepts any number of arguments; if this number is not 1, its + contents are treated as a single "inferred <mrow>" containing its + arguments + */ + if (GetSmImport().GetNodeStack().size() - nElementCount != 1) + SmXMLRowContext_Impl::endFastElement(nElement); + + SmToken aToken; + aToken.cMathChar = u""_ustr; + aToken.nLevel = 5; + aToken.eType = TPHANTOM; + + std::unique_ptr<SmFontNode> pPhantom(new SmFontNode(aToken)); + SmNodeStack& rNodeStack = GetSmImport().GetNodeStack(); + pPhantom->SetSubNodes(nullptr, popOrZero(rNodeStack)); + rNodeStack.push_front(std::move(pPhantom)); +} + +namespace +{ +class SmXMLFencedContext_Impl : public SmXMLRowContext_Impl +{ +protected: + OUString cBegin; + OUString cEnd; + bool bIsStretchy; + +public: + SmXMLFencedContext_Impl(SmXMLImport& rImport) + : SmXMLRowContext_Impl(rImport) + , cBegin('(') + , cEnd(')') + , bIsStretchy(false) + { + } + + void SAL_CALL startFastElement( + sal_Int32 nElement, const uno::Reference<xml::sax::XFastAttributeList>& xAttrList) override; + void SAL_CALL endFastElement(sal_Int32 nElement) override; +}; +} + +void SmXMLFencedContext_Impl::startFastElement( + sal_Int32 /*nElement*/, const uno::Reference<xml::sax::XFastAttributeList>& xAttrList) +{ + for (auto& aIter : sax_fastparser::castToFastAttributeList(xAttrList)) + { + switch (aIter.getToken()) + { + //temp, starmath cannot handle multichar brackets (I think) + case XML_OPEN: + cBegin = aIter.toString(); + break; + case XML_CLOSE: + cEnd = aIter.toString(); + break; + case XML_STRETCHY: + bIsStretchy = IsXMLToken(aIter, XML_TRUE); + break; + default: + XMLOFF_WARN_UNKNOWN("starmath", aIter); + /*Go to superclass*/ + break; + } + } +} + +void SmXMLFencedContext_Impl::endFastElement(sal_Int32 /*nElement*/) +{ + SmToken aToken; + aToken.cMathChar = u""_ustr; + aToken.aText = ","; + aToken.nLevel = 5; + + std::unique_ptr<SmStructureNode> pSNode(new SmBraceNode(aToken)); + if (bIsStretchy) + aToken = starmathdatabase::Identify_PrefixPostfix_SmXMLOperatorContext_Impl(cBegin); + else + aToken = starmathdatabase::Identify_Prefix_SmXMLOperatorContext_Impl(cBegin); + if (aToken.eType == TERROR) + aToken = SmToken(TLPARENT, MS_LPARENT, "(", TG::LBrace, 5); + std::unique_ptr<SmNode> pLeft(new SmMathSymbolNode(aToken)); + if (bIsStretchy) + aToken = starmathdatabase::Identify_PrefixPostfix_SmXMLOperatorContext_Impl(cEnd); + else + aToken = starmathdatabase::Identify_Postfix_SmXMLOperatorContext_Impl(cEnd); + if (aToken.eType == TERROR) + aToken = SmToken(TRPARENT, MS_RPARENT, ")", TG::LBrace, 5); + std::unique_ptr<SmNode> pRight(new SmMathSymbolNode(aToken)); + + SmNodeArray aRelationArray; + SmNodeStack& rNodeStack = GetSmImport().GetNodeStack(); + aToken.cMathChar = u""_ustr; + aToken.eType = TIDENT; + + auto i = rNodeStack.size() - nElementCount; + if (rNodeStack.size() - nElementCount > 1) + i += rNodeStack.size() - 1 - nElementCount; + aRelationArray.resize(i); + while (rNodeStack.size() > nElementCount) + { + auto pNode = std::move(rNodeStack.front()); + rNodeStack.pop_front(); + aRelationArray[--i] = pNode.release(); + if (i > 1 && rNodeStack.size() > 1) + aRelationArray[--i] = new SmGlyphSpecialNode(aToken); + } + + SmToken aDummy; + std::unique_ptr<SmStructureNode> pBody(new SmExpressionNode(aDummy)); + pBody->SetSubNodes(std::move(aRelationArray)); + + pSNode->SetSubNodes(std::move(pLeft), std::move(pBody), std::move(pRight)); + // mfenced is always scalable. Stretchy keyword is not official, but in case of been in there + // can be used as a hint. + pSNode->SetScaleMode(SmScaleMode::Height); + GetSmImport().GetNodeStack().push_front(std::move(pSNode)); +} + +namespace +{ +class SmXMLErrorContext_Impl : public SmXMLRowContext_Impl +{ +public: + SmXMLErrorContext_Impl(SmXMLImport& rImport) + : SmXMLRowContext_Impl(rImport) + { + } + + void SAL_CALL endFastElement(sal_Int32 nElement) override; +}; +} + +void SmXMLErrorContext_Impl::endFastElement(sal_Int32 /*nElement*/) +{ + /*Right now the error tag is completely ignored, what + can I do with it in starmath, ?, maybe we need a + report window ourselves, do a test for validity of + the xml input, use mirrors, and then generate + the markup inside the merror with a big red colour + of something. For now just throw them all away. + */ + SmNodeStack& rNodeStack = GetSmImport().GetNodeStack(); + while (rNodeStack.size() > nElementCount) + { + rNodeStack.pop_front(); + } +} + +namespace +{ +class SmXMLNumberContext_Impl : public SmXMLImportContext +{ +protected: + SmToken aToken; + +public: + SmXMLNumberContext_Impl(SmXMLImport& rImport) + : SmXMLImportContext(rImport) + { + aToken.cMathChar = u""_ustr; + aToken.nLevel = 5; + aToken.eType = TNUMBER; + } + + virtual void TCharacters(const OUString& rChars) override; + + void SAL_CALL endFastElement(sal_Int32 nElement) override; +}; +} + +void SmXMLNumberContext_Impl::TCharacters(const OUString& rChars) { aToken.aText = rChars; } + +void SmXMLNumberContext_Impl::endFastElement(sal_Int32) +{ + GetSmImport().GetNodeStack().push_front(std::make_unique<SmTextNode>(aToken, FNT_NUMBER)); +} + +namespace +{ +class SmXMLAnnotationContext_Impl : public SmXMLImportContext +{ + sal_uInt8 mnStarMathVersion; + +public: + SmXMLAnnotationContext_Impl(SmXMLImport& rImport) + : SmXMLImportContext(rImport) + , mnStarMathVersion(0) + { + } + + void SAL_CALL characters(const OUString& rChars) override; + + void SAL_CALL startFastElement( + sal_Int32 nElement, const uno::Reference<xml::sax::XFastAttributeList>& xAttrList) override; +}; +} + +void SmXMLAnnotationContext_Impl::startFastElement( + sal_Int32 /*nElement*/, const uno::Reference<xml::sax::XFastAttributeList>& xAttrList) +{ + for (auto& aIter : sax_fastparser::castToFastAttributeList(xAttrList)) + { + // sometimes they have namespace, sometimes not? + switch (aIter.getToken() & TOKEN_MASK) + { + case XML_ENCODING: + mnStarMathVersion + = aIter.toView() == "StarMath 5.0" ? 5 : aIter.toView() == "StarMath 6" ? 6 : 0; + break; + default: + XMLOFF_WARN_UNKNOWN("starmath", aIter); + break; + } + } +} + +void SmXMLAnnotationContext_Impl::characters(const OUString& rChars) +{ + if (mnStarMathVersion) + { + GetSmImport().SetText(GetSmImport().GetText() + rChars); + GetSmImport().SetSmSyntaxVersion(mnStarMathVersion); + } +} + +namespace +{ +class SmXMLTextContext_Impl : public SmXMLImportContext +{ +protected: + SmToken aToken; + +public: + SmXMLTextContext_Impl(SmXMLImport& rImport) + : SmXMLImportContext(rImport) + { + aToken.cMathChar = u""_ustr; + aToken.nLevel = 5; + aToken.eType = TTEXT; + } + + virtual void TCharacters(const OUString& rChars) override; + + void SAL_CALL endFastElement(sal_Int32 nElement) override; +}; +} + +void SmXMLTextContext_Impl::TCharacters(const OUString& rChars) { aToken.aText = rChars; } + +void SmXMLTextContext_Impl::endFastElement(sal_Int32) +{ + GetSmImport().GetNodeStack().push_front(std::make_unique<SmTextNode>(aToken, FNT_TEXT)); +} + +namespace +{ +class SmXMLStringContext_Impl : public SmXMLImportContext +{ +protected: + SmToken aToken; + +public: + SmXMLStringContext_Impl(SmXMLImport& rImport) + : SmXMLImportContext(rImport) + { + aToken.cMathChar = u""_ustr; + aToken.nLevel = 5; + aToken.eType = TTEXT; + } + + virtual void TCharacters(const OUString& rChars) override; + + void SAL_CALL endFastElement(sal_Int32 nElement) override; +}; +} + +void SmXMLStringContext_Impl::TCharacters(const OUString& rChars) +{ + /* + The content of <ms> elements should be rendered with visible "escaping" of + certain characters in the content, including at least "double quote" + itself, and preferably whitespace other than individual blanks. The intent + is for the viewer to see that the expression is a string literal, and to + see exactly which characters form its content. For example, <ms>double + quote is "</ms> might be rendered as "double quote is \"". + + Obviously this isn't fully done here. + */ + aToken.aText = "\"" + rChars + "\""; +} + +void SmXMLStringContext_Impl::endFastElement(sal_Int32) +{ + GetSmImport().GetNodeStack().push_front(std::make_unique<SmTextNode>(aToken, FNT_FIXED)); +} + +namespace +{ +class SmXMLIdentifierContext_Impl : public SmXMLImportContext +{ + SmXMLTokenAttrHelper maTokenAttrHelper; + SmXMLContext_Helper aStyleHelper; + SmToken aToken; + +public: + SmXMLIdentifierContext_Impl(SmXMLImport& rImport) + : SmXMLImportContext(rImport) + , maTokenAttrHelper(*this) + , aStyleHelper(*this) + { + aToken.cMathChar = u""_ustr; + aToken.nLevel = 5; + aToken.eType = TIDENT; + } + + void TCharacters(const OUString& rChars) override; + void SAL_CALL + startFastElement(sal_Int32 /*nElement*/, + const uno::Reference<xml::sax::XFastAttributeList>& xAttrList) override + { + maTokenAttrHelper.RetrieveAttrs(xAttrList); + aStyleHelper.RetrieveAttrs(xAttrList); + }; + void SAL_CALL endFastElement(sal_Int32 nElement) override; +}; +} + +void SmXMLIdentifierContext_Impl::endFastElement(sal_Int32) +{ + std::unique_ptr<SmTextNode> pNode; + //we will handle identifier italic/normal here instead of with a standalone + //font node + if (((aStyleHelper.nIsItalic == -1) && (aToken.aText.getLength() > 1)) + || ((aStyleHelper.nIsItalic == 0) && (aToken.aText.getLength() == 1))) + { + pNode.reset(new SmTextNode(aToken, FNT_FUNCTION)); + pNode->GetFont().SetItalic(ITALIC_NONE); + aStyleHelper.nIsItalic = -1; + } + else + pNode.reset(new SmTextNode(aToken, FNT_VARIABLE)); + if (aStyleHelper.nIsItalic != -1) + { + if (aStyleHelper.nIsItalic) + pNode->GetFont().SetItalic(ITALIC_NORMAL); + else + pNode->GetFont().SetItalic(ITALIC_NONE); + aStyleHelper.nIsItalic = -1; + } + GetSmImport().GetNodeStack().push_front(std::move(pNode)); + aStyleHelper.ApplyAttrs(); + + maTokenAttrHelper.ApplyAttrs((aToken.aText.getLength() == 1) ? MathMLMathvariantValue::Italic + : MathMLMathvariantValue::Normal); +} + +void SmXMLIdentifierContext_Impl::TCharacters(const OUString& rChars) { aToken.aText = rChars; } + +namespace +{ +class SmXMLOperatorContext_Impl : public SmXMLImportContext +{ + SmXMLTokenAttrHelper maTokenAttrHelper; + bool bIsStretchy; + bool bIsFenced; + bool isPrefix; + bool isInfix; + bool isPostfix; + SmToken aToken; + +public: + SmXMLOperatorContext_Impl(SmXMLImport& rImport) + : SmXMLImportContext(rImport) + , maTokenAttrHelper(*this) + , bIsStretchy(false) + , bIsFenced(false) + , isPrefix(false) + , isInfix(false) + , isPostfix(false) + { + aToken.eType = TSPECIAL; + aToken.nLevel = 5; + } + + void TCharacters(const OUString& rChars) override; + void SAL_CALL startFastElement( + sal_Int32 nElement, const uno::Reference<xml::sax::XFastAttributeList>& xAttrList) override; + void SAL_CALL endFastElement(sal_Int32 nElement) override; +}; +} + +void SmXMLOperatorContext_Impl::TCharacters(const OUString& rChars) +{ + aToken.setChar(rChars); + SmToken bToken; + if (bIsFenced) + { + if (isPrefix) + bToken = starmathdatabase::Identify_Prefix_SmXMLOperatorContext_Impl(aToken.cMathChar); + else if (isInfix) + bToken = SmToken(TMLINE, MS_VERTLINE, "mline", TG::NONE, 0); + else if (isPostfix) + bToken = starmathdatabase::Identify_Postfix_SmXMLOperatorContext_Impl(aToken.cMathChar); + else + bToken = starmathdatabase::Identify_PrefixPostfix_SmXMLOperatorContext_Impl( + aToken.cMathChar); + } + else + bToken + = starmathdatabase::Identify_SmXMLOperatorContext_Impl(aToken.cMathChar, bIsStretchy); + if (bToken.eType != TERROR) + aToken = bToken; +} + +void SmXMLOperatorContext_Impl::endFastElement(sal_Int32) +{ + std::unique_ptr<SmMathSymbolNode> pNode(new SmMathSymbolNode(aToken)); + //For stretchy scaling the scaling must be retrieved from this node + //and applied to the expression itself so as to get the expression + //to scale the operator to the height of the expression itself + if (bIsStretchy) + pNode->SetScaleMode(SmScaleMode::Height); + GetSmImport().GetNodeStack().push_front(std::move(pNode)); + + // TODO: apply to non-alphabetic characters too + if (rtl::isAsciiAlpha(aToken.cMathChar[0])) + maTokenAttrHelper.ApplyAttrs(MathMLMathvariantValue::Normal); +} + +void SmXMLOperatorContext_Impl::startFastElement( + sal_Int32 /*nElement*/, const uno::Reference<xml::sax::XFastAttributeList>& xAttrList) +{ + maTokenAttrHelper.RetrieveAttrs(xAttrList); + + for (auto& aIter : sax_fastparser::castToFastAttributeList(xAttrList)) + { + switch (aIter.getToken()) + { + case XML_STRETCHY: + bIsStretchy = IsXMLToken(aIter, XML_TRUE); + break; + case XML_FENCE: + bIsFenced = IsXMLToken(aIter, XML_TRUE); + break; + case XML_FORM: + isPrefix = IsXMLToken(aIter, XML_PREFIX); // < + isInfix = IsXMLToken(aIter, XML_INFIX); // | + isPostfix = IsXMLToken(aIter, XML_POSTFIX); // > + break; + default: + XMLOFF_WARN_UNKNOWN("starmath", aIter); + break; + } + } +} + +namespace +{ +class SmXMLSpaceContext_Impl : public SmXMLImportContext +{ +public: + SmXMLSpaceContext_Impl(SmXMLImport& rImport) + : SmXMLImportContext(rImport) + { + } + + void SAL_CALL startFastElement( + sal_Int32 nElement, const uno::Reference<xml::sax::XFastAttributeList>& xAttrList) override; +}; + +bool lcl_CountBlanks(const MathMLAttributeLengthValue& rLV, sal_Int32* pWide, sal_Int32* pNarrow) +{ + assert(pWide); + assert(pNarrow); + if (rLV.aNumber.GetNumerator() == 0) + { + *pWide = *pNarrow = 0; + return true; + } + // TODO: honor other units than em + if (rLV.eUnit != MathMLLengthUnit::Em) + return false; + if (rLV.aNumber.GetNumerator() < 0) + return false; + const Fraction aTwo(2, 1); + auto aWide = rLV.aNumber / aTwo; + auto nWide = static_cast<sal_Int32>(static_cast<tools::Long>(aWide)); + if (nWide < 0) + return false; + const Fraction aPointFive(1, 2); + auto aNarrow = (rLV.aNumber - Fraction(nWide, 1) * aTwo) / aPointFive; + auto nNarrow = static_cast<sal_Int32>(static_cast<tools::Long>(aNarrow)); + if (nNarrow < 0) + return false; + *pWide = nWide; + *pNarrow = nNarrow; + return true; +} +} + +void SmXMLSpaceContext_Impl::startFastElement( + sal_Int32 /*nElement*/, const uno::Reference<xml::sax::XFastAttributeList>& xAttrList) +{ + // There is no syntax in Math to specify blank nodes of arbitrary size yet. + MathMLAttributeLengthValue aLV; + sal_Int32 nWide = 0, nNarrow = 0; + + for (auto& aIter : sax_fastparser::castToFastAttributeList(xAttrList)) + { + OUString sValue = aIter.toString(); + switch (aIter.getToken()) + { + case XML_WIDTH: + if (!ParseMathMLAttributeLengthValue(o3tl::trim(sValue), aLV) + || !lcl_CountBlanks(aLV, &nWide, &nNarrow)) + SAL_WARN("starmath", "ignore mspace's width: " << sValue); + break; + default: + XMLOFF_WARN_UNKNOWN("starmath", aIter); + break; + } + } + SmToken aToken; + aToken.eType = TBLANK; + aToken.cMathChar = u""_ustr; + aToken.nGroup = TG::Blank; + aToken.nLevel = 5; + std::unique_ptr<SmBlankNode> pBlank(new SmBlankNode(aToken)); + if (nWide > 0) + pBlank->IncreaseBy(aToken, nWide); + if (nNarrow > 0) + { + aToken.eType = TSBLANK; + pBlank->IncreaseBy(aToken, nNarrow); + } + GetSmImport().GetNodeStack().push_front(std::move(pBlank)); +} + +namespace +{ +class SmXMLSubContext_Impl : public SmXMLRowContext_Impl +{ +protected: + void GenericEndElement(SmTokenType eType, SmSubSup aSubSup); + +public: + SmXMLSubContext_Impl(SmXMLImport& rImport) + : SmXMLRowContext_Impl(rImport) + { + } + + void SAL_CALL endFastElement(sal_Int32) override { GenericEndElement(TRSUB, RSUB); } +}; +} + +void SmXMLSubContext_Impl::GenericEndElement(SmTokenType eType, SmSubSup eSubSup) +{ + /*The <msub> element requires exactly 2 arguments.*/ + const bool bNodeCheck = GetSmImport().GetNodeStack().size() - nElementCount == 2; + OSL_ENSURE(bNodeCheck, "Sub has not two arguments"); + if (!bNodeCheck) + return; + + SmToken aToken; + aToken.cMathChar = u""_ustr; + aToken.eType = eType; + std::unique_ptr<SmSubSupNode> pNode(new SmSubSupNode(aToken)); + SmNodeStack& rNodeStack = GetSmImport().GetNodeStack(); + + // initialize subnodes array + SmNodeArray aSubNodes; + aSubNodes.resize(1 + SUBSUP_NUM_ENTRIES); + for (size_t i = 1; i < aSubNodes.size(); i++) + aSubNodes[i] = nullptr; + + aSubNodes[eSubSup + 1] = popOrZero(rNodeStack).release(); + aSubNodes[0] = popOrZero(rNodeStack).release(); + pNode->SetSubNodes(std::move(aSubNodes)); + rNodeStack.push_front(std::move(pNode)); +} + +namespace +{ +class SmXMLSupContext_Impl : public SmXMLSubContext_Impl +{ +public: + SmXMLSupContext_Impl(SmXMLImport& rImport) + : SmXMLSubContext_Impl(rImport) + { + } + + void SAL_CALL endFastElement(sal_Int32) override { GenericEndElement(TRSUP, RSUP); } +}; + +class SmXMLSubSupContext_Impl : public SmXMLRowContext_Impl +{ +protected: + void GenericEndElement(SmTokenType eType, SmSubSup aSub, SmSubSup aSup); + +public: + SmXMLSubSupContext_Impl(SmXMLImport& rImport) + : SmXMLRowContext_Impl(rImport) + { + } + + void SAL_CALL endFastElement(sal_Int32) override { GenericEndElement(TRSUB, RSUB, RSUP); } +}; +} + +void SmXMLSubSupContext_Impl::GenericEndElement(SmTokenType eType, SmSubSup aSub, SmSubSup aSup) +{ + /*The <msub> element requires exactly 3 arguments.*/ + const bool bNodeCheck = GetSmImport().GetNodeStack().size() - nElementCount == 3; + OSL_ENSURE(bNodeCheck, "SubSup has not three arguments"); + if (!bNodeCheck) + return; + + SmToken aToken; + aToken.cMathChar = u""_ustr; + aToken.eType = eType; + std::unique_ptr<SmSubSupNode> pNode(new SmSubSupNode(aToken)); + SmNodeStack& rNodeStack = GetSmImport().GetNodeStack(); + + // initialize subnodes array + SmNodeArray aSubNodes; + aSubNodes.resize(1 + SUBSUP_NUM_ENTRIES); + for (size_t i = 1; i < aSubNodes.size(); i++) + aSubNodes[i] = nullptr; + + aSubNodes[aSup + 1] = popOrZero(rNodeStack).release(); + aSubNodes[aSub + 1] = popOrZero(rNodeStack).release(); + aSubNodes[0] = popOrZero(rNodeStack).release(); + pNode->SetSubNodes(std::move(aSubNodes)); + rNodeStack.push_front(std::move(pNode)); +} + +namespace +{ +class SmXMLUnderContext_Impl : public SmXMLSubContext_Impl +{ +protected: + sal_Int16 nAttrCount; + +public: + SmXMLUnderContext_Impl(SmXMLImport& rImport) + : SmXMLSubContext_Impl(rImport) + , nAttrCount(0) + { + } + + void SAL_CALL startFastElement( + sal_Int32 nElement, const uno::Reference<xml::sax::XFastAttributeList>& xAttrList) override; + void SAL_CALL endFastElement(sal_Int32 nElement) override; + void HandleAccent(); +}; +} + +void SmXMLUnderContext_Impl::startFastElement( + sal_Int32 /*nElement*/, const uno::Reference<xml::sax::XFastAttributeList>& xAttrList) +{ + sax_fastparser::FastAttributeList& rAttribList + = sax_fastparser::castToFastAttributeList(xAttrList); + nAttrCount = rAttribList.getFastAttributeTokens().size(); +} + +void SmXMLUnderContext_Impl::HandleAccent() +{ + const bool bNodeCheck = GetSmImport().GetNodeStack().size() - nElementCount == 2; + OSL_ENSURE(bNodeCheck, "Sub has not two arguments"); + if (!bNodeCheck) + return; + + /*Just one special case for the underline thing*/ + SmNodeStack& rNodeStack = GetSmImport().GetNodeStack(); + std::unique_ptr<SmNode> pTest = popOrZero(rNodeStack); + SmToken aToken; + aToken.cMathChar = u""_ustr; + aToken.eType = TUNDERLINE; + + std::unique_ptr<SmNode> pFirst; + std::unique_ptr<SmStructureNode> pNode(new SmAttributeNode(aToken)); + if ((pTest->GetToken().cMathChar[0] & 0x0FFF) == 0x0332) + { + pFirst.reset(new SmRectangleNode(aToken)); + } + else + pFirst = std::move(pTest); + + std::unique_ptr<SmNode> pSecond = popOrZero(rNodeStack); + pNode->SetSubNodes(std::move(pFirst), std::move(pSecond)); + pNode->SetScaleMode(SmScaleMode::Width); + rNodeStack.push_front(std::move(pNode)); +} + +void SmXMLUnderContext_Impl::endFastElement(sal_Int32) +{ + if (!nAttrCount) + GenericEndElement(TCSUB, CSUB); + else + HandleAccent(); +} + +namespace +{ +class SmXMLOverContext_Impl : public SmXMLSubContext_Impl +{ +protected: + sal_Int16 nAttrCount; + +public: + SmXMLOverContext_Impl(SmXMLImport& rImport) + : SmXMLSubContext_Impl(rImport) + , nAttrCount(0) + { + } + + void SAL_CALL endFastElement(sal_Int32 nElement) override; + void SAL_CALL startFastElement( + sal_Int32 nElement, const uno::Reference<xml::sax::XFastAttributeList>& xAttrList) override; + void HandleAccent(); +}; +} + +void SmXMLOverContext_Impl::startFastElement( + sal_Int32 /*nElement*/, const uno::Reference<xml::sax::XFastAttributeList>& xAttrList) +{ + sax_fastparser::FastAttributeList& rAttribList + = sax_fastparser::castToFastAttributeList(xAttrList); + nAttrCount = rAttribList.getFastAttributeTokens().size(); +} + +void SmXMLOverContext_Impl::endFastElement(sal_Int32) +{ + if (!nAttrCount) + GenericEndElement(TCSUP, CSUP); + else + HandleAccent(); +} + +void SmXMLOverContext_Impl::HandleAccent() +{ + const bool bNodeCheck = GetSmImport().GetNodeStack().size() - nElementCount == 2; + OSL_ENSURE(bNodeCheck, "Sub has not two arguments"); + if (!bNodeCheck) + return; + + SmToken aToken; + aToken.cMathChar = u""_ustr; + aToken.eType = TACUTE; + + std::unique_ptr<SmAttributeNode> pNode(new SmAttributeNode(aToken)); + SmNodeStack& rNodeStack = GetSmImport().GetNodeStack(); + + std::unique_ptr<SmNode> pFirst = popOrZero(rNodeStack); + std::unique_ptr<SmNode> pSecond = popOrZero(rNodeStack); + pNode->SetSubNodes(std::move(pFirst), std::move(pSecond)); + pNode->SetScaleMode(SmScaleMode::Width); + rNodeStack.push_front(std::move(pNode)); +} + +namespace +{ +class SmXMLUnderOverContext_Impl : public SmXMLSubSupContext_Impl +{ +public: + SmXMLUnderOverContext_Impl(SmXMLImport& rImport) + : SmXMLSubSupContext_Impl(rImport) + { + } + + void SAL_CALL endFastElement(sal_Int32) override { GenericEndElement(TCSUB, CSUB, CSUP); } +}; + +class SmXMLMultiScriptsContext_Impl : public SmXMLSubSupContext_Impl +{ + bool bHasPrescripts; + + void ProcessSubSupPairs(bool bIsPrescript); + +public: + SmXMLMultiScriptsContext_Impl(SmXMLImport& rImport) + : SmXMLSubSupContext_Impl(rImport) + , bHasPrescripts(false) + { + } + + void SAL_CALL endFastElement(sal_Int32 nElement) override; + virtual uno::Reference<xml::sax::XFastContextHandler> SAL_CALL createFastChildContext( + sal_Int32 nElement, const uno::Reference<xml::sax::XFastAttributeList>& xAttrList) override; +}; + +class SmXMLNoneContext_Impl : public SmXMLImportContext +{ +public: + SmXMLNoneContext_Impl(SmXMLImport& rImport) + : SmXMLImportContext(rImport) + { + } + + void SAL_CALL endFastElement(sal_Int32 nElement) override; +}; +} + +void SmXMLNoneContext_Impl::endFastElement(sal_Int32) +{ + SmToken aToken; + aToken.cMathChar = u""_ustr; + aToken.aText.clear(); + aToken.nLevel = 5; + aToken.eType = TIDENT; + GetSmImport().GetNodeStack().push_front(std::make_unique<SmTextNode>(aToken, FNT_VARIABLE)); +} + +namespace +{ +class SmXMLPrescriptsContext_Impl : public SmXMLImportContext +{ +public: + SmXMLPrescriptsContext_Impl(SmXMLImport& rImport) + : SmXMLImportContext(rImport) + { + } +}; + +class SmXMLTableRowContext_Impl : public SmXMLRowContext_Impl +{ +public: + SmXMLTableRowContext_Impl(SmXMLImport& rImport) + : SmXMLRowContext_Impl(rImport) + { + } + + virtual uno::Reference<xml::sax::XFastContextHandler> SAL_CALL createFastChildContext( + sal_Int32 nElement, const uno::Reference<xml::sax::XFastAttributeList>& xAttrList) override; +}; + +class SmXMLTableContext_Impl : public SmXMLTableRowContext_Impl +{ +public: + SmXMLTableContext_Impl(SmXMLImport& rImport) + : SmXMLTableRowContext_Impl(rImport) + { + } + + void SAL_CALL endFastElement(sal_Int32 nElement) override; + virtual uno::Reference<xml::sax::XFastContextHandler> SAL_CALL createFastChildContext( + sal_Int32 nElement, const uno::Reference<xml::sax::XFastAttributeList>& xAttrList) override; +}; + +class SmXMLTableCellContext_Impl : public SmXMLRowContext_Impl +{ +public: + SmXMLTableCellContext_Impl(SmXMLImport& rImport) + : SmXMLRowContext_Impl(rImport) + { + } +}; + +class SmXMLAlignGroupContext_Impl : public SmXMLRowContext_Impl +{ +public: + SmXMLAlignGroupContext_Impl(SmXMLImport& rImport) + : SmXMLRowContext_Impl(rImport) + { + } + + /*Don't do anything with alignment for now*/ +}; + +class SmXMLActionContext_Impl : public SmXMLRowContext_Impl +{ + size_t mnSelection; // 1-based + +public: + SmXMLActionContext_Impl(SmXMLImport& rImport) + : SmXMLRowContext_Impl(rImport) + , mnSelection(1) + { + } + + void SAL_CALL startFastElement( + sal_Int32 nElement, const uno::Reference<xml::sax::XFastAttributeList>& xAttrList) override; + void SAL_CALL endFastElement(sal_Int32 nElement) override; +}; + +// NB: virtually inherit so we can multiply inherit properly +// in SmXMLFlatDocContext_Impl +class SmXMLOfficeContext_Impl : public virtual SvXMLImportContext +{ +public: + SmXMLOfficeContext_Impl(SmXMLImport& rImport) + : SvXMLImportContext(rImport) + { + } + + virtual css::uno::Reference<css::xml::sax::XFastContextHandler> SAL_CALL createFastChildContext( + sal_Int32 nElement, + const css::uno::Reference<css::xml::sax::XFastAttributeList>& xAttrList) override; +}; +} + +uno::Reference<xml::sax::XFastContextHandler> SmXMLOfficeContext_Impl::createFastChildContext( + sal_Int32 nElement, const uno::Reference<xml::sax::XFastAttributeList>& /*xAttrList*/) +{ + if (nElement == XML_ELEMENT(OFFICE, XML_META)) + { + SAL_WARN("starmath", + "XML_TOK_DOC_META: should not have come here, maybe document is invalid?"); + } + else if (nElement == XML_ELEMENT(OFFICE, XML_SETTINGS)) + { + return new XMLDocumentSettingsContext(GetImport()); + } + return nullptr; +} + +namespace +{ +// context for flat file xml format +class SmXMLFlatDocContext_Impl : public SmXMLOfficeContext_Impl, public SvXMLMetaDocumentContext +{ +public: + SmXMLFlatDocContext_Impl(SmXMLImport& i_rImport, + const uno::Reference<document::XDocumentProperties>& i_xDocProps); + + virtual css::uno::Reference<css::xml::sax::XFastContextHandler> SAL_CALL createFastChildContext( + sal_Int32 nElement, + const css::uno::Reference<css::xml::sax::XFastAttributeList>& xAttrList) override; +}; +} + +SmXMLFlatDocContext_Impl::SmXMLFlatDocContext_Impl( + SmXMLImport& i_rImport, const uno::Reference<document::XDocumentProperties>& i_xDocProps) + : SvXMLImportContext(i_rImport) + , SmXMLOfficeContext_Impl(i_rImport) + , SvXMLMetaDocumentContext(i_rImport, i_xDocProps) +{ +} + +uno::Reference<xml::sax::XFastContextHandler> + SAL_CALL SmXMLFlatDocContext_Impl::createFastChildContext( + sal_Int32 nElement, const uno::Reference<xml::sax::XFastAttributeList>& xAttrList) +{ + // behave like meta base class iff we encounter office:meta + if (nElement == XML_ELEMENT(OFFICE, XML_META)) + { + return SvXMLMetaDocumentContext::createFastChildContext(nElement, xAttrList); + } + else + { + return SmXMLOfficeContext_Impl::createFastChildContext(nElement, xAttrList); + } +} + +uno::Reference<xml::sax::XFastContextHandler> SmXMLDocContext_Impl::createFastChildContext( + sal_Int32 nElement, const uno::Reference<xml::sax::XFastAttributeList>& /*xAttrList*/) +{ + uno::Reference<xml::sax::XFastContextHandler> xContext; + + switch (nElement) + { + //Consider semantics a dummy except for any starmath annotations + case XML_ELEMENT(MATH, XML_SEMANTICS): + xContext = new SmXMLRowContext_Impl(GetSmImport()); + break; + /*General Layout Schemata*/ + case XML_ELEMENT(MATH, XML_MROW): + xContext = new SmXMLRowContext_Impl(GetSmImport()); + break; + case XML_ELEMENT(MATH, XML_MENCLOSE): + xContext = new SmXMLEncloseContext_Impl(GetSmImport()); + break; + case XML_ELEMENT(MATH, XML_MFRAC): + xContext = new SmXMLFracContext_Impl(GetSmImport()); + break; + case XML_ELEMENT(MATH, XML_MSQRT): + xContext = new SmXMLSqrtContext_Impl(GetSmImport()); + break; + case XML_ELEMENT(MATH, XML_MROOT): + xContext = new SmXMLRootContext_Impl(GetSmImport()); + break; + case XML_ELEMENT(MATH, XML_MSTYLE): + xContext = new SmXMLStyleContext_Impl(GetSmImport()); + break; + case XML_ELEMENT(MATH, XML_MERROR): + xContext = new SmXMLErrorContext_Impl(GetSmImport()); + break; + case XML_ELEMENT(MATH, XML_MPADDED): + xContext = new SmXMLPaddedContext_Impl(GetSmImport()); + break; + case XML_ELEMENT(MATH, XML_MPHANTOM): + xContext = new SmXMLPhantomContext_Impl(GetSmImport()); + break; + case XML_ELEMENT(MATH, XML_MFENCED): + xContext = new SmXMLFencedContext_Impl(GetSmImport()); + break; + /*Script and Limit Schemata*/ + case XML_ELEMENT(MATH, XML_MSUB): + xContext = new SmXMLSubContext_Impl(GetSmImport()); + break; + case XML_ELEMENT(MATH, XML_MSUP): + xContext = new SmXMLSupContext_Impl(GetSmImport()); + break; + case XML_ELEMENT(MATH, XML_MSUBSUP): + xContext = new SmXMLSubSupContext_Impl(GetSmImport()); + break; + case XML_ELEMENT(MATH, XML_MUNDER): + xContext = new SmXMLUnderContext_Impl(GetSmImport()); + break; + case XML_ELEMENT(MATH, XML_MOVER): + xContext = new SmXMLOverContext_Impl(GetSmImport()); + break; + case XML_ELEMENT(MATH, XML_MUNDEROVER): + xContext = new SmXMLUnderOverContext_Impl(GetSmImport()); + break; + case XML_ELEMENT(MATH, XML_MMULTISCRIPTS): + xContext = new SmXMLMultiScriptsContext_Impl(GetSmImport()); + break; + case XML_ELEMENT(MATH, XML_MTABLE): + xContext = new SmXMLTableContext_Impl(GetSmImport()); + break; + case XML_ELEMENT(MATH, XML_MACTION): + xContext = new SmXMLActionContext_Impl(GetSmImport()); + break; + default: + /*Basically there's an implicit mrow around certain bare + *elements, use a RowContext to see if this is one of + *those ones*/ + rtl::Reference<SmXMLRowContext_Impl> aTempContext( + new SmXMLRowContext_Impl(GetSmImport())); + + xContext = aTempContext->StrictCreateChildContext(nElement); + break; + } + return xContext; +} + +void SmXMLDocContext_Impl::endFastElement(sal_Int32) +{ + SmNodeStack& rNodeStack = GetSmImport().GetNodeStack(); + + std::unique_ptr<SmNode> pContextNode = popOrZero(rNodeStack); + + SmToken aDummy; + std::unique_ptr<SmStructureNode> pSNode(new SmLineNode(aDummy)); + pSNode->SetSubNodes(std::move(pContextNode), nullptr); + rNodeStack.push_front(std::move(pSNode)); + + SmNodeArray LineArray; + auto n = rNodeStack.size(); + LineArray.resize(n); + for (size_t j = 0; j < n; j++) + { + auto pNode = std::move(rNodeStack.front()); + rNodeStack.pop_front(); + LineArray[n - (j + 1)] = pNode.release(); + } + std::unique_ptr<SmStructureNode> pSNode2(new SmTableNode(aDummy)); + pSNode2->SetSubNodes(std::move(LineArray)); + rNodeStack.push_front(std::move(pSNode2)); +} + +void SmXMLFracContext_Impl::endFastElement(sal_Int32) +{ + SmNodeStack& rNodeStack = GetSmImport().GetNodeStack(); + const bool bNodeCheck = rNodeStack.size() - nElementCount == 2; + OSL_ENSURE(bNodeCheck, "Fraction (mfrac) tag is missing component"); + if (!bNodeCheck) + return; + + SmToken aToken; + aToken.cMathChar = u""_ustr; + aToken.eType = TFRAC; + std::unique_ptr<SmStructureNode> pSNode(new SmBinVerNode(aToken)); + std::unique_ptr<SmNode> pOper(new SmRectangleNode(aToken)); + std::unique_ptr<SmNode> pSecond = popOrZero(rNodeStack); + std::unique_ptr<SmNode> pFirst = popOrZero(rNodeStack); + pSNode->SetSubNodes(std::move(pFirst), std::move(pOper), std::move(pSecond)); + rNodeStack.push_front(std::move(pSNode)); +} + +void SmXMLRootContext_Impl::endFastElement(sal_Int32) +{ + /*The <mroot> element requires exactly 2 arguments.*/ + const bool bNodeCheck = GetSmImport().GetNodeStack().size() - nElementCount == 2; + OSL_ENSURE(bNodeCheck, "Root tag is missing component"); + if (!bNodeCheck) + return; + + SmToken aToken; + aToken.setChar(MS_SQRT); //Temporary: alert, based on StarSymbol font + aToken.eType = TNROOT; + std::unique_ptr<SmStructureNode> pSNode(new SmRootNode(aToken)); + std::unique_ptr<SmNode> pOper(new SmRootSymbolNode(aToken)); + SmNodeStack& rNodeStack = GetSmImport().GetNodeStack(); + std::unique_ptr<SmNode> pIndex = popOrZero(rNodeStack); + std::unique_ptr<SmNode> pBase = popOrZero(rNodeStack); + pSNode->SetSubNodes(std::move(pIndex), std::move(pOper), std::move(pBase)); + rNodeStack.push_front(std::move(pSNode)); +} + +void SmXMLSqrtContext_Impl::endFastElement(sal_Int32 nElement) +{ + /* + <msqrt> accepts any number of arguments; if this number is not 1, its + contents are treated as a single "inferred <mrow>" containing its + arguments + */ + if (GetSmImport().GetNodeStack().size() - nElementCount != 1) + SmXMLRowContext_Impl::endFastElement(nElement); + + SmToken aToken; + aToken.setChar(MS_SQRT); //Temporary: alert, based on StarSymbol font + aToken.eType = TSQRT; + std::unique_ptr<SmStructureNode> pSNode(new SmRootNode(aToken)); + std::unique_ptr<SmNode> pOper(new SmRootSymbolNode(aToken)); + SmNodeStack& rNodeStack = GetSmImport().GetNodeStack(); + pSNode->SetSubNodes(nullptr, std::move(pOper), popOrZero(rNodeStack)); + rNodeStack.push_front(std::move(pSNode)); +} + +void SmXMLRowContext_Impl::endFastElement(sal_Int32) +{ + SmNodeArray aRelationArray; + SmNodeStack& rNodeStack = GetSmImport().GetNodeStack(); + + if (rNodeStack.size() > nElementCount) + { + auto nSize = rNodeStack.size() - nElementCount; + + aRelationArray.resize(nSize); + for (auto j = nSize; j > 0; j--) + { + auto pNode = std::move(rNodeStack.front()); + rNodeStack.pop_front(); + aRelationArray[j - 1] = pNode.release(); + } + + //If the first or last element is an operator with stretchyness + //set then we must create a brace node here from those elements, + //removing the stretchness from the operators and applying it to + //ourselves, and creating the appropriate dummy StarMath none bracket + //to balance the arrangement + if (((aRelationArray[0]->GetScaleMode() == SmScaleMode::Height) + && (aRelationArray[0]->GetType() == SmNodeType::Math)) + || ((aRelationArray[nSize - 1]->GetScaleMode() == SmScaleMode::Height) + && (aRelationArray[nSize - 1]->GetType() == SmNodeType::Math))) + { + SmToken aToken; + aToken.cMathChar = u""_ustr; + aToken.nLevel = 5; + + int nLeft = 0, nRight = 0; + if ((aRelationArray[0]->GetScaleMode() == SmScaleMode::Height) + && (aRelationArray[0]->GetType() == SmNodeType::Math)) + { + aToken = aRelationArray[0]->GetToken(); + nLeft = 1; + } + else + aToken.cMathChar = u""_ustr; + + aToken.eType = TLPARENT; + std::unique_ptr<SmNode> pLeft(new SmMathSymbolNode(aToken)); + + if ((aRelationArray[nSize - 1]->GetScaleMode() == SmScaleMode::Height) + && (aRelationArray[nSize - 1]->GetType() == SmNodeType::Math)) + { + aToken = aRelationArray[nSize - 1]->GetToken(); + nRight = 1; + } + else + aToken.cMathChar = u""_ustr; + + aToken.eType = TRPARENT; + std::unique_ptr<SmNode> pRight(new SmMathSymbolNode(aToken)); + + SmNodeArray aRelationArray2; + + //!! nSize-nLeft-nRight may be < 0 !! + int nRelArrSize = nSize - nLeft - nRight; + if (nRelArrSize > 0) + { + aRelationArray2.resize(nRelArrSize); + for (int i = 0; i < nRelArrSize; i++) + { + aRelationArray2[i] = aRelationArray[i + nLeft]; + aRelationArray[i + nLeft] = nullptr; + } + } + + SmToken aDummy; + std::unique_ptr<SmStructureNode> pSNode(new SmBraceNode(aToken)); + std::unique_ptr<SmStructureNode> pBody(new SmExpressionNode(aDummy)); + pBody->SetSubNodes(std::move(aRelationArray2)); + + pSNode->SetSubNodes(std::move(pLeft), std::move(pBody), std::move(pRight)); + pSNode->SetScaleMode(SmScaleMode::Height); + rNodeStack.push_front(std::move(pSNode)); + + for (auto a : aRelationArray) + delete a; + + return; + } + } + else + { + // The elements msqrt, mstyle, merror, menclose, mpadded, mphantom, mtd, and math + // treat their content as a single inferred mrow in case their content is empty. + // Here an empty group {} is used to catch those cases and transform them without error + // to StarMath. + aRelationArray.resize(2); + SmToken aToken; + aToken.setChar(MS_LBRACE); + aToken.nLevel = 5; + aToken.eType = TLGROUP; + aToken.nGroup = TG::NONE; + aToken.aText = "{"; + aRelationArray[0] = new SmLineNode(aToken); + + aToken.setChar(MS_RBRACE); + aToken.nLevel = 0; + aToken.eType = TRGROUP; + aToken.nGroup = TG::NONE; + aToken.aText = "}"; + aRelationArray[1] = new SmLineNode(aToken); + } + + SmToken aDummy; + std::unique_ptr<SmStructureNode> pSNode(new SmExpressionNode(aDummy)); + pSNode->SetSubNodes(std::move(aRelationArray)); + rNodeStack.push_front(std::move(pSNode)); +} + +uno::Reference<xml::sax::XFastContextHandler> +SmXMLRowContext_Impl::StrictCreateChildContext(sal_Int32 nElement) +{ + uno::Reference<xml::sax::XFastContextHandler> pContext; + + switch (nElement) + { + /*Note that these should accept malignmark subelements, but do not*/ + case XML_ELEMENT(MATH, XML_MN): + pContext = new SmXMLNumberContext_Impl(GetSmImport()); + break; + case XML_ELEMENT(MATH, XML_MI): + pContext = new SmXMLIdentifierContext_Impl(GetSmImport()); + break; + case XML_ELEMENT(MATH, XML_MO): + pContext = new SmXMLOperatorContext_Impl(GetSmImport()); + break; + case XML_ELEMENT(MATH, XML_MTEXT): + pContext = new SmXMLTextContext_Impl(GetSmImport()); + break; + case XML_ELEMENT(MATH, XML_MSPACE): + pContext = new SmXMLSpaceContext_Impl(GetSmImport()); + break; + case XML_ELEMENT(MATH, XML_MS): + pContext = new SmXMLStringContext_Impl(GetSmImport()); + break; + + /*Note: The maligngroup should only be seen when the row + * (or descendants) are in a table*/ + case XML_ELEMENT(MATH, XML_MALIGNGROUP): + pContext = new SmXMLAlignGroupContext_Impl(GetSmImport()); + break; + + case XML_ELEMENT(MATH, XML_ANNOTATION): + pContext = new SmXMLAnnotationContext_Impl(GetSmImport()); + break; + + default: + break; + } + return pContext; +} + +uno::Reference<xml::sax::XFastContextHandler> SmXMLRowContext_Impl::createFastChildContext( + sal_Int32 nElement, const uno::Reference<xml::sax::XFastAttributeList>& xAttrList) +{ + uno::Reference<xml::sax::XFastContextHandler> xContext = StrictCreateChildContext(nElement); + + if (!xContext) + { + //Hmm, unrecognized for this level, check to see if it's + //an element that can have an implicit schema around it + xContext = SmXMLDocContext_Impl::createFastChildContext(nElement, xAttrList); + } + return xContext; +} + +uno::Reference<xml::sax::XFastContextHandler> SmXMLMultiScriptsContext_Impl::createFastChildContext( + sal_Int32 nElement, const uno::Reference<xml::sax::XFastAttributeList>& xAttrList) +{ + uno::Reference<xml::sax::XFastContextHandler> xContext; + + switch (nElement) + { + case XML_ELEMENT(MATH, XML_MPRESCRIPTS): + bHasPrescripts = true; + ProcessSubSupPairs(false); + xContext = new SmXMLPrescriptsContext_Impl(GetSmImport()); + break; + case XML_ELEMENT(MATH, XML_NONE): + xContext = new SmXMLNoneContext_Impl(GetSmImport()); + break; + default: + xContext = SmXMLRowContext_Impl::createFastChildContext(nElement, xAttrList); + break; + } + return xContext; +} + +void SmXMLMultiScriptsContext_Impl::ProcessSubSupPairs(bool bIsPrescript) +{ + SmNodeStack& rNodeStack = GetSmImport().GetNodeStack(); + + if (rNodeStack.size() <= nElementCount) + return; + + auto nCount = rNodeStack.size() - nElementCount - 1; + if (nCount == 0) + return; + + if (nCount % 2 == 0) + { + SmToken aToken; + aToken.cMathChar = u""_ustr; + aToken.eType = bIsPrescript ? TLSUB : TRSUB; + + SmNodeStack aReverseStack; + for (size_t i = 0; i < nCount + 1; i++) + { + auto pNode = std::move(rNodeStack.front()); + rNodeStack.pop_front(); + aReverseStack.push_front(std::move(pNode)); + } + + SmSubSup eSub = bIsPrescript ? LSUB : RSUB; + SmSubSup eSup = bIsPrescript ? LSUP : RSUP; + + for (size_t i = 0; i < nCount; i += 2) + { + std::unique_ptr<SmSubSupNode> pNode(new SmSubSupNode(aToken)); + + // initialize subnodes array + SmNodeArray aSubNodes(1 + SUBSUP_NUM_ENTRIES); + + /*On each loop the base and its sub sup pair becomes the + base for the next loop to which the next sub sup pair is + attached, i.e. wheels within wheels*/ + aSubNodes[0] = popOrZero(aReverseStack).release(); + + std::unique_ptr<SmNode> pScriptNode = popOrZero(aReverseStack); + + if (pScriptNode + && ((pScriptNode->GetToken().eType != TIDENT) + || (!pScriptNode->GetToken().aText.isEmpty()))) + aSubNodes[eSub + 1] = pScriptNode.release(); + pScriptNode = popOrZero(aReverseStack); + if (pScriptNode + && ((pScriptNode->GetToken().eType != TIDENT) + || (!pScriptNode->GetToken().aText.isEmpty()))) + aSubNodes[eSup + 1] = pScriptNode.release(); + + pNode->SetSubNodes(std::move(aSubNodes)); + aReverseStack.push_front(std::move(pNode)); + } + assert(!aReverseStack.empty()); + auto pNode = std::move(aReverseStack.front()); + aReverseStack.pop_front(); + rNodeStack.push_front(std::move(pNode)); + } + else + { + // Ignore odd number of elements. + for (size_t i = 0; i < nCount; i++) + { + rNodeStack.pop_front(); + } + } +} + +void SmXMLTableContext_Impl::endFastElement(sal_Int32) +{ + SmNodeArray aExpressionArray; + SmNodeStack& rNodeStack = GetSmImport().GetNodeStack(); + SmNodeStack aReverseStack; + aExpressionArray.resize(rNodeStack.size() - nElementCount); + + size_t nRows = rNodeStack.size() - nElementCount; + size_t nCols = 0; + + for (size_t i = nRows; i > 0; --i) + { + SmNode* pArray = rNodeStack.front().release(); + rNodeStack.pop_front(); + if (pArray->GetNumSubNodes() == 0) + { + //This is a little tricky, it is possible that there was + //be elements that were not inside a <mtd> pair, in which + //case they will not be in a row, i.e. they will not have + //SubNodes, so we have to wait until here before we can + //resolve the situation. Implicit surrounding tags are + //surprisingly difficult to get right within this + //architecture + + SmNodeArray aRelationArray; + aRelationArray.resize(1); + aRelationArray[0] = pArray; + SmToken aDummy; + SmExpressionNode* pExprNode = new SmExpressionNode(aDummy); + pExprNode->SetSubNodes(std::move(aRelationArray)); + pArray = pExprNode; + } + + nCols = std::max(nCols, pArray->GetNumSubNodes()); + aReverseStack.push_front(std::unique_ptr<SmNode>(pArray)); + } + if (nCols > SAL_MAX_UINT16) + throw std::range_error("column limit"); + if (nRows > SAL_MAX_UINT16) + throw std::range_error("row limit"); + aExpressionArray.resize(nCols * nRows); + size_t j = 0; + for (auto& elem : aReverseStack) + { + std::unique_ptr<SmStructureNode> xArray(static_cast<SmStructureNode*>(elem.release())); + for (size_t i = 0; i < xArray->GetNumSubNodes(); ++i) + aExpressionArray[j++] = xArray->GetSubNode(i); + xArray->ClearSubNodes(); + } + aReverseStack.clear(); + + SmToken aToken; + aToken.cMathChar = u""_ustr; + aToken.eType = TMATRIX; + std::unique_ptr<SmMatrixNode> pSNode(new SmMatrixNode(aToken)); + pSNode->SetSubNodes(std::move(aExpressionArray)); + pSNode->SetRowCol(nRows, nCols); + rNodeStack.push_front(std::move(pSNode)); +} + +uno::Reference<xml::sax::XFastContextHandler> SmXMLTableRowContext_Impl::createFastChildContext( + sal_Int32 nElement, const uno::Reference<xml::sax::XFastAttributeList>& xAttrList) +{ + uno::Reference<xml::sax::XFastContextHandler> xContext; + + switch (nElement) + { + case XML_ELEMENT(MATH, XML_MTD): + xContext = new SmXMLTableCellContext_Impl(GetSmImport()); + break; + default: + xContext = SmXMLRowContext_Impl::createFastChildContext(nElement, xAttrList); + break; + } + return xContext; +} + +uno::Reference<xml::sax::XFastContextHandler> SmXMLTableContext_Impl::createFastChildContext( + sal_Int32 nElement, const uno::Reference<xml::sax::XFastAttributeList>& xAttrList) +{ + uno::Reference<xml::sax::XFastContextHandler> xContext; + + switch (nElement) + { + case XML_ELEMENT(MATH, XML_MTR): + xContext = new SmXMLTableRowContext_Impl(GetSmImport()); + break; + default: + xContext = SmXMLTableRowContext_Impl::createFastChildContext(nElement, xAttrList); + break; + } + return xContext; +} + +void SmXMLMultiScriptsContext_Impl::endFastElement(sal_Int32) +{ + ProcessSubSupPairs(bHasPrescripts); +} + +void SmXMLActionContext_Impl::startFastElement( + sal_Int32 /*nElement*/, const uno::Reference<xml::sax::XFastAttributeList>& xAttrList) +{ + for (auto& aIter : sax_fastparser::castToFastAttributeList(xAttrList)) + { + switch (aIter.getToken()) + { + case XML_SELECTION: + { + sal_Int32 n = aIter.toInt32(); + if (n > 0) + mnSelection = static_cast<size_t>(n); + } + break; + default: + XMLOFF_WARN_UNKNOWN("starmath", aIter); + break; + } + } +} + +void SmXMLActionContext_Impl::endFastElement(sal_Int32) +{ + SmNodeStack& rNodeStack = GetSmImport().GetNodeStack(); + auto nSize = rNodeStack.size(); + if (nSize <= nElementCount) + { + // not compliant to maction's specification, e.g., no subexpressions + return; + } + assert(mnSelection > 0); + if (nSize < nElementCount + mnSelection) + { + // No selected subexpression exists, which is a MathML error; + // fallback to selecting the first + mnSelection = 1; + } + assert(nSize >= nElementCount + mnSelection); + for (auto i = nSize - (nElementCount + mnSelection); i > 0; i--) + { + rNodeStack.pop_front(); + } + auto pSelected = std::move(rNodeStack.front()); + rNodeStack.pop_front(); + for (auto i = rNodeStack.size() - nElementCount; i > 0; i--) + { + rNodeStack.pop_front(); + } + rNodeStack.push_front(std::move(pSelected)); +} + +SvXMLImportContext* +SmXMLImport::CreateFastContext(sal_Int32 nElement, + const uno::Reference<xml::sax::XFastAttributeList>& /*xAttrList*/) +{ + SvXMLImportContext* pContext = nullptr; + + switch (nElement) + { + case XML_ELEMENT(OFFICE, XML_DOCUMENT): + case XML_ELEMENT(OFFICE, XML_DOCUMENT_META): + { + uno::Reference<document::XDocumentPropertiesSupplier> xDPS(GetModel(), + uno::UNO_QUERY_THROW); + pContext = ((nElement & TOKEN_MASK) == XML_DOCUMENT_META) + ? new SvXMLMetaDocumentContext(*this, xDPS->getDocumentProperties()) + // flat OpenDocument file format -- this has not been tested... + : new SmXMLFlatDocContext_Impl(*this, xDPS->getDocumentProperties()); + } + break; + default: + if (IsTokenInNamespace(nElement, XML_NAMESPACE_OFFICE)) + pContext = new SmXMLOfficeContext_Impl(*this); + else + pContext = new SmXMLDocContext_Impl(*this); + } + return pContext; +} + +SmXMLImport::~SmXMLImport() noexcept { cleanup(); } + +void SmXMLImport::SetViewSettings(const Sequence<PropertyValue>& aViewProps) +{ + uno::Reference<frame::XModel> xModel = GetModel(); + if (!xModel.is()) + return; + + SmModel* pModel = dynamic_cast<SmModel*>(xModel.get()); + + if (!pModel) + return; + + SmDocShell* pDocShell = static_cast<SmDocShell*>(pModel->GetObjectShell()); + if (!pDocShell) + return; + + tools::Rectangle aRect(pDocShell->GetVisArea()); + + tools::Long nTmp = 0; + + for (const PropertyValue& rValue : aViewProps) + { + if (rValue.Name == "ViewAreaTop") + { + rValue.Value >>= nTmp; + aRect.SaturatingSetPosY(nTmp); + } + else if (rValue.Name == "ViewAreaLeft") + { + rValue.Value >>= nTmp; + aRect.SaturatingSetPosX(nTmp); + } + else if (rValue.Name == "ViewAreaWidth") + { + rValue.Value >>= nTmp; + Size aSize(aRect.GetSize()); + aSize.setWidth(nTmp); + aRect.SaturatingSetSize(aSize); + } + else if (rValue.Name == "ViewAreaHeight") + { + rValue.Value >>= nTmp; + Size aSize(aRect.GetSize()); + aSize.setHeight(nTmp); + aRect.SaturatingSetSize(aSize); + } + } + + pDocShell->SetVisArea(aRect); +} + +void SmXMLImport::SetConfigurationSettings(const Sequence<PropertyValue>& aConfProps) +{ + uno::Reference<XPropertySet> xProps(GetModel(), UNO_QUERY); + if (!xProps.is()) + return; + + Reference<XPropertySetInfo> xInfo(xProps->getPropertySetInfo()); + if (!xInfo.is()) + return; + + static constexpr OUStringLiteral sFormula(u"Formula"); + static constexpr OUStringLiteral sBasicLibraries(u"BasicLibraries"); + static constexpr OUStringLiteral sDialogLibraries(u"DialogLibraries"); + for (const PropertyValue& rValue : aConfProps) + { + if (rValue.Name != sFormula && rValue.Name != sBasicLibraries + && rValue.Name != sDialogLibraries) + { + try + { + if (xInfo->hasPropertyByName(rValue.Name)) + xProps->setPropertyValue(rValue.Name, rValue.Value); + } + catch (const beans::PropertyVetoException&) + { + // dealing with read-only properties here. Nothing to do... + } + catch (const Exception&) + { + DBG_UNHANDLED_EXCEPTION("starmath"); + } + } + } +} + +extern "C" SAL_DLLPUBLIC_EXPORT bool TestImportMML(SvStream& rStream) +{ + SmGlobals::ensure(); + + SfxObjectShellLock xDocSh(new SmDocShell(SfxModelFlags::EMBEDDED_OBJECT)); + xDocSh->DoInitNew(); + uno::Reference<frame::XModel> xModel(xDocSh->GetModel()); + + uno::Reference<beans::XPropertySet> xInfoSet; + uno::Reference<uno::XComponentContext> xContext(comphelper::getProcessComponentContext()); + uno::Reference<io::XInputStream> xStream(new utl::OSeekableInputStreamWrapper(rStream)); + + //SetLoading hack because the document properties will be re-initted + //by the xml filter and during the init, while it's considered uninitialized, + //setting a property will inform the document it's modified, which attempts + //to update the properties, which throws cause the properties are uninitialized + xDocSh->SetLoading(SfxLoadedFlags::NONE); + + ErrCode nRet = ERRCODE_SFX_DOLOADFAILED; + + try + { + nRet = SmXMLImportWrapper::ReadThroughComponent(xStream, xModel, xContext, xInfoSet, + "com.sun.star.comp.Math.XMLImporter", false, + false); + } + catch (...) + { + } + + xDocSh->SetLoading(SfxLoadedFlags::ALL); + + xDocSh->DoClose(); + + return nRet != ERRCODE_NONE; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/mathml/starmathdatabase.cxx b/starmath/source/mathml/starmathdatabase.cxx new file mode 100644 index 0000000000..6eb6d209c8 --- /dev/null +++ b/starmath/source/mathml/starmathdatabase.cxx @@ -0,0 +1,794 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <starmathdatabase.hxx> +#include <types.hxx> + +SmToken starmathdatabase::Identify_SmXMLOperatorContext_Impl(std::u16string_view rText, + bool bIsStretchy, sal_Int32 nIndex) +{ + auto cChar = o3tl::iterateCodePoints(rText, &nIndex); + switch (cChar) + { + case MS_COPROD: + return SmToken(TCOPROD, MS_COPROD, "coprod", TG::Oper, 5); + case MS_IIINT: + return SmToken(TIIINT, MS_IIINT, "iiint", TG::Oper, 5); + case MS_IINT: + return SmToken(TIINT, MS_IINT, "iint", TG::Oper, 5); + case MS_INT: + if (bIsStretchy) + return SmToken(TINTD, MS_INT, "intd", TG::Oper, 5); + else + return SmToken(TINT, MS_INT, "int", TG::Oper, 5); + case MS_LINT: + return SmToken(TLINT, MS_LINT, "lint", TG::Oper, 5); + case MS_LLINT: + return SmToken(TLLINT, MS_LLINT, "llint", TG::Oper, 5); + case MS_LLLINT: + return SmToken(TLLLINT, MS_LLLINT, "lllint", TG::Oper, 5); + case MS_PROD: + return SmToken(TPROD, MS_PROD, "prod", TG::Oper, 5); + case MS_SUM: + return SmToken(TSUM, MS_SUM, "sum", TG::Oper, 5); + case MS_MAJ: + return SmToken(TSUM, MS_MAJ, "maj", TG::Oper, 5); + case MS_FACT: + return SmToken(TFACT, MS_FACT, "!", TG::UnOper, 5); + case MS_NEG: + return SmToken(TNEG, MS_NEG, "neg", TG::UnOper, 5); + case MS_OMINUS: + return SmToken(TOMINUS, MS_OMINUS, "ominus", TG::Sum, 0); + case MS_OPLUS: + return SmToken(TOPLUS, MS_OPLUS, "oplus", TG::Sum, 0); + case MS_UNION: + return SmToken(TUNION, MS_UNION, "union", TG::Sum, 0); + case MS_OR: + return SmToken(TOR, MS_OR, "|", TG::Sum, 5); + case MS_PLUSMINUS: + return SmToken(TPLUSMINUS, MS_PLUSMINUS, "+-", TG::Sum | TG::UnOper, 5); + case MS_MINUSPLUS: + return SmToken(TMINUSPLUS, MS_MINUSPLUS, "-+", TG::Sum | TG::UnOper, 5); + case 0xe083: + case MS_PLUS: + return SmToken(TPLUS, MS_PLUS, "+", TG::Sum | TG::UnOper, 5); + case MS_MINUS: + return SmToken(TMINUS, MS_MINUS, "-", TG::Sum | TG::UnOper, 5); + case 0x2022: + case MS_CDOT: + return SmToken(TCDOT, MS_CDOT, "cdot", TG::Product, 0); + case MS_DIV: + return SmToken(TDIV, MS_DIV, "div", TG::Product, 0); + case MS_TIMES: + return SmToken(TTIMES, MS_TIMES, "times", TG::Product, 0); + case MS_INTERSECT: + return SmToken(TINTERSECT, MS_INTERSECT, "intersection", TG::Product, 0); + case MS_ODIVIDE: + return SmToken(TODIVIDE, MS_ODIVIDE, "odivide", TG::Product, 0); + case MS_ODOT: + return SmToken(TODOT, MS_ODOT, "odot", TG::Product, 0); + case MS_OTIMES: + return SmToken(TOTIMES, MS_OTIMES, "otimes", TG::Product, 0); + case MS_AND: + return SmToken(TAND, MS_AND, "&", TG::Product, 0); + case MS_MULTIPLY: + return SmToken(TMULTIPLY, MS_MULTIPLY, "*", TG::Product, 0); + case MS_SLASH: + if (bIsStretchy) + return SmToken(TWIDESLASH, MS_SLASH, "wideslash", TG::Product, 0); + else + return SmToken(TSLASH, MS_SLASH, "slash", TG::Product, 0); + case MS_BACKSLASH: + if (bIsStretchy) + return SmToken(TWIDEBACKSLASH, MS_BACKSLASH, "widebslash", TG::Product, 0); + else + return SmToken(TBACKSLASH, MS_BACKSLASH, "bslash", TG::Product, 0); + case MS_DEF: + return SmToken(TDEF, MS_DEF, "def", TG::Relation, 0); + case MS_LINE: + return SmToken(TDIVIDES, MS_LINE, "divides", TG::Relation, 0); + case MS_EQUIV: + return SmToken(TEQUIV, MS_EQUIV, "equiv", TG::Relation, 0); + case MS_GE: + return SmToken(TGE, MS_GE, ">=", TG::Relation, 0); + case MS_GESLANT: + return SmToken(TGESLANT, MS_GESLANT, "geslant", TG::Relation, 0); + case MS_GG: + return SmToken(TGG, MS_GG, ">>", TG::Relation, 0); + case MS_GT: + return SmToken(TGT, MS_GT, ">", TG::Relation, 0); + case MS_IN: + return SmToken(TIN, MS_IN, "in", TG::Relation, 0); + case MS_LE: + return SmToken(TLE, MS_LE, "<=", TG::Relation, 0); + case MS_LESLANT: + return SmToken(TLESLANT, MS_LESLANT, "leslant", TG::Relation, 0); + case MS_LL: + return SmToken(TLL, MS_LL, "<<", TG::Relation, 0); + case MS_LT: + return SmToken(TLT, MS_LT, "<", TG::Relation, 0); + case MS_NDIVIDES: + return SmToken(TNDIVIDES, MS_NDIVIDES, "ndivides", TG::Relation, 0); + case MS_NEQ: + return SmToken(TNEQ, MS_NEQ, "<>", TG::Relation, 0); + case MS_NOTIN: + return SmToken(TNOTIN, MS_NOTIN, "notin", TG::Relation, 0); + case MS_NOTPRECEDES: + return SmToken(TNOTPRECEDES, MS_NOTPRECEDES, "nprec", TG::Relation, 0); + case MS_NSUBSET: + return SmToken(TNSUBSET, MS_NSUBSET, "nsubset", TG::Relation, 0); + case MS_NSUBSETEQ: + return SmToken(TNSUBSETEQ, MS_NSUBSETEQ, "nsubseteq", TG::Relation, 0); + case MS_NOTSUCCEEDS: + return SmToken(TNOTSUCCEEDS, MS_NOTSUCCEEDS, "nsucc", TG::Relation, 0); + case MS_NSUPSET: + return SmToken(TNSUPSET, MS_NSUPSET, "nsupset", TG::Relation, 0); + case MS_NSUPSETEQ: + return SmToken(TNSUPSETEQ, MS_NSUPSETEQ, "nsupseteq", TG::Relation, 0); + case MS_ORTHO: + return SmToken(TORTHO, MS_ORTHO, "ortho", TG::Relation, 0); + case MS_NI: + return SmToken(TNI, MS_NI, "owns", TG::Relation, 0); + case MS_DLINE: + return SmToken(TPARALLEL, MS_DLINE, "parallel", TG::Relation, 0); + case MS_PRECEDES: + return SmToken(TPRECEDES, MS_PRECEDES, "prec", TG::Relation, 0); + case MS_PRECEDESEQUAL: + return SmToken(TPRECEDESEQUAL, MS_PRECEDESEQUAL, "preccurlyeq", TG::Relation, 0); + case MS_PRECEDESEQUIV: + return SmToken(TPRECEDESEQUIV, MS_PRECEDESEQUIV, "precsim", TG::Relation, 0); + case MS_PROP: + return SmToken(TPROP, MS_PROP, "prop", TG::Relation, 0); + case MS_SIM: + return SmToken(TSIM, MS_SIM, "sim", TG::Relation, 0); + case 0x2245: + case MS_SIMEQ: + return SmToken(TSIMEQ, MS_SIMEQ, "simeq", TG::Relation, 0); + case MS_SUBSET: + return SmToken(TSUBSET, MS_SUBSET, "subset", TG::Relation, 0); + case MS_SUBSETEQ: + return SmToken(TSUBSETEQ, MS_SUBSETEQ, "subseteq", TG::Relation, 0); + case MS_SUCCEEDS: + return SmToken(TSUCCEEDS, MS_SUCCEEDS, "succ", TG::Relation, 0); + case MS_SUCCEEDSEQUAL: + return SmToken(TSUCCEEDSEQUAL, MS_SUCCEEDSEQUAL, "succcurlyeq", TG::Relation, 0); + case MS_SUCCEEDSEQUIV: + return SmToken(TSUCCEEDSEQUIV, MS_SUCCEEDSEQUIV, "succsim", TG::Relation, 0); + case MS_SUPSET: + return SmToken(TSUPSET, MS_SUPSET, "supset", TG::Relation, 0); + case MS_SUPSETEQ: + return SmToken(TSUPSETEQ, MS_SUPSETEQ, "supseteq", TG::Relation, 0); + case MS_RIGHTARROW: + return SmToken(TTOWARD, MS_RIGHTARROW, "toward", TG::Relation, 0); + case MS_TRANSL: + return SmToken(TTRANSL, MS_TRANSL, "transl", TG::Relation, 0); + case MS_TRANSR: + return SmToken(TTRANSR, MS_TRANSR, "transr", TG::Relation, 0); + case MS_ASSIGN: + return SmToken(TASSIGN, MS_ASSIGN, "=", TG::Relation, 0); + case MS_LANGLE: + return SmToken(TLANGLE, MS_LMATHANGLE, "langle", TG::LBrace, 5); + case MS_LMATHANGLE: + return SmToken(TLANGLE, MS_LMATHANGLE, "langle", TG::LBrace, 5); + case MS_LBRACE: + return SmToken(TLBRACE, MS_LBRACE, "lbrace", TG::LBrace, 5); + case MS_LCEIL: + return SmToken(TLCEIL, MS_LCEIL, "lceil", TG::LBrace, 5); + case MS_LFLOOR: + return SmToken(TLFLOOR, MS_LFLOOR, "lfloor", TG::LBrace, 5); + case MS_LDBRACKET: + return SmToken(TLDBRACKET, MS_LDBRACKET, "ldbracket", TG::LBrace, 5); + case MS_LBRACKET: + return SmToken(TLBRACKET, MS_LBRACKET, "[", TG::LBrace, 5); + case MS_LPARENT: + return SmToken(TLPARENT, MS_LPARENT, "(", TG::LBrace, 5); + case MS_RANGLE: + return SmToken(TRANGLE, MS_RMATHANGLE, "rangle", TG::RBrace, 5); + case MS_RMATHANGLE: + return SmToken(TRANGLE, MS_RMATHANGLE, "rangle", TG::RBrace, 5); + case MS_RBRACE: + return SmToken(TRBRACE, MS_RBRACE, "rbrace", TG::RBrace, 5); + case MS_RCEIL: + return SmToken(TRCEIL, MS_RCEIL, "rceil", TG::RBrace, 5); + case MS_RFLOOR: + return SmToken(TRFLOOR, MS_RFLOOR, "rfloor", TG::RBrace, 5); + case MS_RDBRACKET: + return SmToken(TRDBRACKET, MS_RDBRACKET, "rdbracket", TG::RBrace, 5); + case MS_RBRACKET: + return SmToken(TRBRACKET, MS_RBRACKET, "]", TG::RBrace, 5); + case MS_RPARENT: + return SmToken(TRPARENT, MS_RPARENT, ")", TG::RBrace, 5); + case MS_NONE: + return SmToken(TNONE, MS_NONE, "none", TG::RBrace | TG::LBrace, 5); + default: + return SmToken(TERROR, MS_NONE, "", TG::NONE, SAL_MAX_UINT16); + } +} + +SmToken starmathdatabase::Identify_Prefix_SmXMLOperatorContext_Impl(std::u16string_view rText, + sal_Int32 nIndex) +{ + auto cChar = o3tl::iterateCodePoints(rText, &nIndex); + switch (cChar) + { + case MS_VERTLINE: + return SmToken(TLLINE, MS_VERTLINE, "lline", TG::LBrace, 5); + case MS_DVERTLINE: + return SmToken(TLDLINE, MS_DVERTLINE, "ldline", TG::LBrace, 5); + case MS_LANGLE: + return SmToken(TLANGLE, MS_LMATHANGLE, "langle", TG::LBrace, 5); + case MS_LMATHANGLE: + return SmToken(TLANGLE, MS_LMATHANGLE, "langle", TG::LBrace, 5); + case MS_LBRACE: + return SmToken(TLBRACE, MS_LBRACE, "lbrace", TG::LBrace, 5); + case MS_LCEIL: + return SmToken(TLCEIL, MS_LCEIL, "lceil", TG::LBrace, 5); + case MS_LFLOOR: + return SmToken(TLFLOOR, MS_LFLOOR, "lfloor", TG::LBrace, 5); + case MS_LDBRACKET: + return SmToken(TLDBRACKET, MS_LDBRACKET, "ldbracket", TG::LBrace, 5); + case MS_LBRACKET: + return SmToken(TLBRACKET, MS_LBRACKET, "[", TG::LBrace, 5); + case MS_LPARENT: + return SmToken(TLPARENT, MS_LPARENT, "(", TG::LBrace, 5); + case MS_RANGLE: + return SmToken(TRANGLE, MS_RMATHANGLE, "rangle", TG::RBrace, 5); + case MS_RMATHANGLE: + return SmToken(TRANGLE, MS_RMATHANGLE, "rangle", TG::RBrace, 5); + case MS_RBRACE: + return SmToken(TRBRACE, MS_RBRACE, "rbrace", TG::RBrace, 5); + case MS_RCEIL: + return SmToken(TRCEIL, MS_RCEIL, "rceil", TG::RBrace, 5); + case MS_RFLOOR: + return SmToken(TRFLOOR, MS_RFLOOR, "rfloor", TG::RBrace, 5); + case MS_RDBRACKET: + return SmToken(TRDBRACKET, MS_RDBRACKET, "rdbracket", TG::RBrace, 5); + case MS_RBRACKET: + return SmToken(TRBRACKET, MS_RBRACKET, "]", TG::RBrace, 5); + case MS_RPARENT: + return SmToken(TRPARENT, MS_RPARENT, ")", TG::RBrace, 5); + case MS_NONE: + return SmToken(TNONE, MS_NONE, "none", TG::LBrace | TG::RBrace, 5); + default: + return SmToken(TERROR, MS_NONE, "", TG::NONE, SAL_MAX_UINT16); + } +} + +SmToken starmathdatabase::Identify_Postfix_SmXMLOperatorContext_Impl(std::u16string_view rText, + sal_Int32 nIndex) +{ + auto cChar = o3tl::iterateCodePoints(rText, &nIndex); + switch (cChar) + { + case MS_VERTLINE: + return SmToken(TRLINE, MS_VERTLINE, "rline", TG::RBrace, 5); + case MS_DVERTLINE: + return SmToken(TRDLINE, MS_DVERTLINE, "rdline", TG::RBrace, 5); + case MS_LANGLE: + return SmToken(TLANGLE, MS_LMATHANGLE, "langle", TG::LBrace, 5); + case MS_LMATHANGLE: + return SmToken(TLANGLE, MS_LMATHANGLE, "langle", TG::LBrace, 5); + case MS_LBRACE: + return SmToken(TLBRACE, MS_LBRACE, "lbrace", TG::LBrace, 5); + case MS_LCEIL: + return SmToken(TLCEIL, MS_LCEIL, "lceil", TG::LBrace, 5); + case MS_LFLOOR: + return SmToken(TLFLOOR, MS_LFLOOR, "lfloor", TG::LBrace, 5); + case MS_LDBRACKET: + return SmToken(TLDBRACKET, MS_LDBRACKET, "ldbracket", TG::LBrace, 5); + case MS_LBRACKET: + return SmToken(TLBRACKET, MS_LBRACKET, "[", TG::LBrace, 5); + case MS_LPARENT: + return SmToken(TLPARENT, MS_LPARENT, "(", TG::LBrace, 5); + case MS_RANGLE: + return SmToken(TRANGLE, MS_RMATHANGLE, "rangle", TG::RBrace, 5); + case MS_RMATHANGLE: + return SmToken(TRANGLE, MS_RMATHANGLE, "rangle", TG::RBrace, 5); + case MS_RBRACE: + return SmToken(TRBRACE, MS_RBRACE, "rbrace", TG::RBrace, 5); + case MS_RCEIL: + return SmToken(TRCEIL, MS_RCEIL, "rceil", TG::RBrace, 5); + case MS_RFLOOR: + return SmToken(TRFLOOR, MS_RFLOOR, "rfloor", TG::RBrace, 5); + case MS_RDBRACKET: + return SmToken(TRDBRACKET, MS_RDBRACKET, "rdbracket", TG::RBrace, 5); + case MS_RBRACKET: + return SmToken(TRBRACKET, MS_RBRACKET, "]", TG::RBrace, 5); + case MS_RPARENT: + return SmToken(TRPARENT, MS_RPARENT, ")", TG::RBrace, 5); + case MS_NONE: + return SmToken(TNONE, MS_NONE, "none", TG::LBrace | TG::RBrace, 5); + default: + return SmToken(TERROR, MS_NONE, "", TG::NONE, SAL_MAX_UINT16); + } +} + +SmToken +starmathdatabase::Identify_PrefixPostfix_SmXMLOperatorContext_Impl(std::u16string_view rText, + sal_Int32 nIndex) +{ + auto cChar = o3tl::iterateCodePoints(rText, &nIndex); + switch (cChar) + { + case MS_VERTLINE: + return SmToken(TLRLINE, MS_VERTLINE, "lrline", TG::LBrace | TG::RBrace, 5); + case MS_DVERTLINE: + return SmToken(TLRDLINE, MS_DVERTLINE, "lrdline", TG::LBrace | TG::RBrace, 5); + case MS_LANGLE: + return SmToken(TLANGLE, MS_LMATHANGLE, "langle", TG::LBrace, 5); + case MS_LMATHANGLE: + return SmToken(TLANGLE, MS_LMATHANGLE, "langle", TG::LBrace, 5); + case MS_LBRACE: + return SmToken(TLBRACE, MS_LBRACE, "lbrace", TG::LBrace, 5); + case MS_LCEIL: + return SmToken(TLCEIL, MS_LCEIL, "lceil", TG::LBrace, 5); + case MS_LFLOOR: + return SmToken(TLFLOOR, MS_LFLOOR, "lfloor", TG::LBrace, 5); + case MS_LDBRACKET: + return SmToken(TLDBRACKET, MS_LDBRACKET, "ldbracket", TG::LBrace, 5); + case MS_LBRACKET: + return SmToken(TLBRACKET, MS_LBRACKET, "[", TG::LBrace, 5); + case MS_LPARENT: + return SmToken(TLPARENT, MS_LPARENT, "(", TG::LBrace, 5); + case MS_RANGLE: + return SmToken(TRANGLE, MS_RMATHANGLE, "rangle", TG::RBrace, 5); + case MS_RMATHANGLE: + return SmToken(TRANGLE, MS_RMATHANGLE, "rangle", TG::RBrace, 5); + case MS_RBRACE: + return SmToken(TRBRACE, MS_RBRACE, "rbrace", TG::RBrace, 5); + case MS_RCEIL: + return SmToken(TRCEIL, MS_RCEIL, "rceil", TG::RBrace, 5); + case MS_RFLOOR: + return SmToken(TRFLOOR, MS_RFLOOR, "rfloor", TG::RBrace, 5); + case MS_RDBRACKET: + return SmToken(TRDBRACKET, MS_RDBRACKET, "rdbracket", TG::RBrace, 5); + case MS_RBRACKET: + return SmToken(TRBRACKET, MS_RBRACKET, "]", TG::RBrace, 5); + case MS_RPARENT: + return SmToken(TRPARENT, MS_RPARENT, ")", TG::RBrace, 5); + case MS_NONE: + return SmToken(TNONE, MS_NONE, "none", TG::LBrace | TG::RBrace, 5); + default: + return SmToken(TERROR, MS_NONE, "", TG::NONE, SAL_MAX_UINT16); + } +} + +const SmColorTokenTableEntry starmathdatabase::aColorTokenTableParse[] + = { { "aliceblue", THTMLCOL, COL_SM_ALICEBLUE }, + { "antiquewhite", THTMLCOL, COL_SM_ANTIQUEWHITE }, + { "aqua", TMATHMLCOL, COL_SM_AQUA }, + { "aquamarine", THTMLCOL, COL_SM_AQUAMARINE }, + { "azure", THTMLCOL, COL_SM_AZURE }, + { "beige", THTMLCOL, COL_SM_BEIGE }, + { "bisque", THTMLCOL, COL_SM_BISQUE }, + { "black", TMATHMLCOL, COL_SM_BLACK }, + { "blanchedalmond", THTMLCOL, COL_SM_BLANCHEDALMOND }, + { "blue", TMATHMLCOL, COL_SM_BLUE }, + { "blueviolet", THTMLCOL, COL_SM_BLUEVIOLET }, + { "brown", THTMLCOL, COL_SM_BROWN }, + { "burlywood", THTMLCOL, COL_SM_BURLYWOOD }, + { "cadetblue", THTMLCOL, COL_SM_CADETBLUE }, + { "chartreuse", THTMLCOL, COL_SM_CHARTREUSE }, + { "chocolate", THTMLCOL, COL_SM_CHOCOLATE }, + { "coral", THTMLCOL, COL_SM_CORAL }, + { "cornflowerblue", THTMLCOL, COL_SM_CORNFLOWERBLUE }, + { "cornsilk", THTMLCOL, COL_SM_CORNSILK }, + { "crimson", THTMLCOL, COL_SM_CRIMSON }, + { "cyan", TMATHMLCOL, COL_SM_CYAN }, + { "darkblue", THTMLCOL, COL_SM_DARKBLUE }, + { "darkcyan", THTMLCOL, COL_SM_DARKCYAN }, + { "darkgoldenrod", THTMLCOL, COL_SM_DARKGOLDENROD }, + { "darkgray", THTMLCOL, COL_SM_DARKGRAY }, + { "darkgreen", THTMLCOL, COL_SM_DARKGREEN }, + { "darkgrey", THTMLCOL, COL_SM_DARKGREY }, + { "darkkhaki", THTMLCOL, COL_SM_DARKKHAKI }, + { "darkmagenta", THTMLCOL, COL_SM_DARKMAGENTA }, + { "darkolivegreen", THTMLCOL, COL_SM_DARKOLIVEGREEN }, + { "darkorange", THTMLCOL, COL_SM_DARKORANGE }, + { "darkorchid", THTMLCOL, COL_SM_DARKORCHID }, + { "darkred", THTMLCOL, COL_SM_DARKRED }, + { "darksalmon", THTMLCOL, COL_SM_DARKSALMON }, + { "darkseagreen", THTMLCOL, COL_SM_DARKSEAGREEN }, + { "darkslateblue", THTMLCOL, COL_SM_DARKSLATEBLUE }, + { "darkslategray", THTMLCOL, COL_SM_DARKSLATEGRAY }, + { "darkslategrey", THTMLCOL, COL_SM_DARKSLATEGREY }, + { "darkturquoise", THTMLCOL, COL_SM_DARKTURQUOISE }, + { "darkviolet", THTMLCOL, COL_SM_DARKVIOLET }, + { "debian", TICONICCOL, COL_SM_DEBIAN_MAGENTA }, + { "deeppink", THTMLCOL, COL_SM_DEEPPINK }, + { "deepskyblue", THTMLCOL, COL_SM_DEEPSKYBLUE }, + { "dimgray", THTMLCOL, COL_SM_DIMGRAY }, + { "dimgrey", THTMLCOL, COL_SM_DIMGREY }, + { "dodgerblue", THTMLCOL, COL_SM_DODGERBLUE }, + { "dvip", TDVIPSNAMESCOL, COL_SM_BLACK }, + { "firebrick", THTMLCOL, COL_SM_FIREBRICK }, + { "floralwhite", THTMLCOL, COL_SM_FLORALWHITE }, + { "forestgreen", THTMLCOL, COL_SM_FORESTGREEN }, + { "fuchsia", TMATHMLCOL, COL_SM_FUCHSIA }, + { "gainsboro", THTMLCOL, COL_SM_GAINSBORO }, + { "ghostwhite", THTMLCOL, COL_SM_GHOSTWHITE }, + { "gold", THTMLCOL, COL_SM_GOLD }, + { "goldenrod", THTMLCOL, COL_SM_GOLDENROD }, + { "gray", TMATHMLCOL, COL_SM_GRAY }, + { "green", TMATHMLCOL, COL_SM_GREEN }, + { "greenyellow", THTMLCOL, COL_SM_GREENYELLOW }, + { "grey", THTMLCOL, COL_SM_GREY }, + { "hex", THEX, COL_SM_BLACK }, + { "honeydew", THTMLCOL, COL_SM_HONEYDEW }, + { "hotpink", THTMLCOL, COL_SM_HOTPINK }, + { "indianred", THTMLCOL, COL_SM_INDIANRED }, + { "indigo", THTMLCOL, COL_SM_INDIGO }, + { "ivory", THTMLCOL, COL_SM_IVORY }, + { "khaki", THTMLCOL, COL_SM_KHAKI }, + { "lavender", THTMLCOL, COL_SM_LAVENDER }, + { "lavenderblush", THTMLCOL, COL_SM_LAVENDERBLUSH }, + { "lawngreen", THTMLCOL, COL_SM_LAWNGREEN }, + { "lemonchiffon", THTMLCOL, COL_SM_LEMONCHIFFON }, + { "lightblue", THTMLCOL, COL_SM_LIGHTBLUE }, + { "lightcoral", THTMLCOL, COL_SM_LIGHTCORAL }, + { "lightcyan", THTMLCOL, COL_SM_LIGHTCYAN }, + { "lightgoldenrodyellow", THTMLCOL, COL_SM_LIGHTGOLDENRODYELLOW }, + { "lightgray", THTMLCOL, COL_SM_LIGHTGRAY }, + { "lightgreen", THTMLCOL, COL_SM_LIGHTGREEN }, + { "lightgrey", THTMLCOL, COL_SM_LIGHTGREY }, + { "lightpink", THTMLCOL, COL_SM_LIGHTPINK }, + { "lightsalmon", THTMLCOL, COL_SM_LIGHTSALMON }, + { "lightseagreen", THTMLCOL, COL_SM_LIGHTSEAGREEN }, + { "lightskyblue", THTMLCOL, COL_SM_LIGHTSKYBLUE }, + { "lightslategray", THTMLCOL, COL_SM_LIGHTSLATEGRAY }, + { "lightslategrey", THTMLCOL, COL_SM_LIGHTSLATEGREY }, + { "lightsteelblue", THTMLCOL, COL_SM_LIGHTSTEELBLUE }, + { "lightyellow", THTMLCOL, COL_SM_LIGHTYELLOW }, + { "lime", TMATHMLCOL, COL_SM_LIME }, + { "limegreen", THTMLCOL, COL_SM_LIMEGREEN }, + { "linen", THTMLCOL, COL_SM_LINEN }, + { "lo", TICONICCOL, COL_SM_LO_GREEN }, + { "magenta", TMATHMLCOL, COL_SM_MAGENTA }, + { "maroon", TMATHMLCOL, COL_SM_MAROON }, + { "mediumaquamarine", THTMLCOL, COL_SM_MEDIUMAQUAMARINE }, + { "mediumblue", THTMLCOL, COL_SM_MEDIUMBLUE }, + { "mediumorchid", THTMLCOL, COL_SM_MEDIUMORCHID }, + { "mediumpurple", THTMLCOL, COL_SM_MEDIUMPURPLE }, + { "mediumseagreen", THTMLCOL, COL_SM_MEDIUMSEAGREEN }, + { "mediumslateblue", THTMLCOL, COL_SM_MEDIUMSLATEBLUE }, + { "mediumspringgreen", THTMLCOL, COL_SM_MEDIUMSPRINGGREEN }, + { "mediumturquoise", THTMLCOL, COL_SM_MEDIUMTURQUOISE }, + { "mediumvioletred", THTMLCOL, COL_SM_MEDIUMVIOLETRED }, + { "midnightblue", THTMLCOL, COL_SM_MIDNIGHTBLUE }, + { "mintcream", THTMLCOL, COL_SM_MINTCREAM }, + { "mistyrose", THTMLCOL, COL_SM_MISTYROSE }, + { "moccasin", THTMLCOL, COL_SM_MOCCASIN }, + { "navajowhite", THTMLCOL, COL_SM_NAVAJOWHITE }, + { "navy", TMATHMLCOL, COL_SM_NAVY }, + { "oldlace", THTMLCOL, COL_SM_OLDLACE }, + { "olive", TMATHMLCOL, COL_SM_OLIVE }, + { "olivedrab", THTMLCOL, COL_SM_OLIVEDRAB }, + { "orange", THTMLCOL, COL_SM_ORANGE }, + { "orangered", THTMLCOL, COL_SM_ORANGERED }, + { "orchid", THTMLCOL, COL_SM_ORCHID }, + { "palegoldenrod", THTMLCOL, COL_SM_PALEGOLDENROD }, + { "palegreen", THTMLCOL, COL_SM_PALEGREEN }, + { "paleturquoise", THTMLCOL, COL_SM_PALETURQUOISE }, + { "palevioletred", THTMLCOL, COL_SM_PALEVIOLETRED }, + { "papayawhip", THTMLCOL, COL_SM_PAPAYAWHIP }, + { "peachpuff", THTMLCOL, COL_SM_PEACHPUFF }, + { "peru", THTMLCOL, COL_SM_PERU }, + { "pink", THTMLCOL, COL_SM_PINK }, + { "plum", THTMLCOL, COL_SM_PLUM }, + { "powderblue", THTMLCOL, COL_SM_POWDERBLUE }, + { "purple", TMATHMLCOL, COL_SM_PURPLE }, + { "rebeccapurple", THTMLCOL, COL_SM_REBECCAPURPLE }, + { "red", TMATHMLCOL, COL_SM_RED }, + { "rgb", TRGB, COL_AUTO }, + { "rgba", TRGBA, COL_AUTO }, + { "rosybrown", THTMLCOL, COL_SM_ROSYBROWN }, + { "royalblue", THTMLCOL, COL_SM_ROYALBLUE }, + { "saddlebrown", THTMLCOL, COL_SM_SADDLEBROWN }, + { "salmon", THTMLCOL, COL_SM_SALMON }, + { "sandybrown", THTMLCOL, COL_SM_SANDYBROWN }, + { "seagreen", THTMLCOL, COL_SM_SEAGREEN }, + { "seashell", THTMLCOL, COL_SM_SEASHELL }, + { "sienna", THTMLCOL, COL_SM_SIENNA }, + { "silver", TMATHMLCOL, COL_SM_SILVER }, + { "skyblue", THTMLCOL, COL_SM_SKYBLUE }, + { "slateblue", THTMLCOL, COL_SM_SLATEBLUE }, + { "slategray", THTMLCOL, COL_SM_SLATEGRAY }, + { "slategrey", THTMLCOL, COL_SM_SLATEGREY }, + { "snow", THTMLCOL, COL_SM_SNOW }, + { "springgreen", THTMLCOL, COL_SM_SPRINGGREEN }, + { "steelblue", THTMLCOL, COL_SM_STEELBLUE }, + { "tan", THTMLCOL, COL_SM_TAN }, + { "teal", TMATHMLCOL, COL_SM_TEAL }, + { "thistle", THTMLCOL, COL_SM_THISTLE }, + { "tomato", THTMLCOL, COL_SM_TOMATO }, + { "turquoise", THTMLCOL, COL_SM_TURQUOISE }, + { "ubuntu", TICONICCOL, COL_SM_UBUNTU_ORANGE }, + { "violet", THTMLCOL, COL_SM_VIOLET }, + { "wheat", THTMLCOL, COL_SM_WHEAT }, + { "white", TMATHMLCOL, COL_SM_WHITE }, + { "whitesmoke", THTMLCOL, COL_SM_WHITESMOKE }, + { "yellow", TMATHMLCOL, COL_SM_YELLOW }, + { "yellowgreen", THTMLCOL, COL_SM_YELLOWGREEN } }; + +const SmColorTokenTableEntry starmathdatabase::aColorTokenTableHTML[] + = { { "aliceblue", THTMLCOL, COL_SM_ALICEBLUE }, + { "antiquewhite", THTMLCOL, COL_SM_ANTIQUEWHITE }, + { "aqua", TMATHMLCOL, COL_SM_AQUA }, + { "aquamarine", THTMLCOL, COL_SM_AQUAMARINE }, + { "azure", THTMLCOL, COL_SM_AZURE }, + { "beige", THTMLCOL, COL_SM_BEIGE }, + { "bisque", THTMLCOL, COL_SM_BISQUE }, + { "black", TMATHMLCOL, COL_SM_BLACK }, + { "blanchedalmond", THTMLCOL, COL_SM_BLANCHEDALMOND }, + { "blue", TMATHMLCOL, COL_SM_BLUE }, + { "blueviolet", THTMLCOL, COL_SM_BLUEVIOLET }, + { "brown", THTMLCOL, COL_SM_BROWN }, + { "burlywood", THTMLCOL, COL_SM_BURLYWOOD }, + { "cadetblue", THTMLCOL, COL_SM_CADETBLUE }, + { "chartreuse", THTMLCOL, COL_SM_CHARTREUSE }, + { "chocolate", THTMLCOL, COL_SM_CHOCOLATE }, + { "coral", THTMLCOL, COL_SM_CORAL }, + { "cornflowerblue", THTMLCOL, COL_SM_CORNFLOWERBLUE }, + { "cornsilk", THTMLCOL, COL_SM_CORNSILK }, + { "crimson", THTMLCOL, COL_SM_CRIMSON }, + { "cyan", TMATHMLCOL, COL_SM_CYAN }, + { "darkblue", THTMLCOL, COL_SM_DARKBLUE }, + { "darkcyan", THTMLCOL, COL_SM_DARKCYAN }, + { "darkgoldenrod", THTMLCOL, COL_SM_DARKGOLDENROD }, + { "darkgray", THTMLCOL, COL_SM_DARKGRAY }, + { "darkgreen", THTMLCOL, COL_SM_DARKGREEN }, + { "darkgrey", THTMLCOL, COL_SM_DARKGREY }, + { "darkkhaki", THTMLCOL, COL_SM_DARKKHAKI }, + { "darkmagenta", THTMLCOL, COL_SM_DARKMAGENTA }, + { "darkolivegreen", THTMLCOL, COL_SM_DARKOLIVEGREEN }, + { "darkorange", THTMLCOL, COL_SM_DARKORANGE }, + { "darkorchid", THTMLCOL, COL_SM_DARKORCHID }, + { "darkred", THTMLCOL, COL_SM_DARKRED }, + { "darksalmon", THTMLCOL, COL_SM_DARKSALMON }, + { "darkseagreen", THTMLCOL, COL_SM_DARKSEAGREEN }, + { "darkslateblue", THTMLCOL, COL_SM_DARKSLATEBLUE }, + { "darkslategray", THTMLCOL, COL_SM_DARKSLATEGRAY }, + { "darkslategrey", THTMLCOL, COL_SM_DARKSLATEGREY }, + { "darkturquoise", THTMLCOL, COL_SM_DARKTURQUOISE }, + { "darkviolet", THTMLCOL, COL_SM_DARKVIOLET }, + { "deeppink", THTMLCOL, COL_SM_DEEPPINK }, + { "deepskyblue", THTMLCOL, COL_SM_DEEPSKYBLUE }, + { "dimgray", THTMLCOL, COL_SM_DIMGRAY }, + { "dimgrey", THTMLCOL, COL_SM_DIMGREY }, + { "dodgerblue", THTMLCOL, COL_SM_DODGERBLUE }, + { "firebrick", THTMLCOL, COL_SM_FIREBRICK }, + { "floralwhite", THTMLCOL, COL_SM_FLORALWHITE }, + { "forestgreen", THTMLCOL, COL_SM_FORESTGREEN }, + { "fuchsia", TMATHMLCOL, COL_SM_FUCHSIA }, + { "gainsboro", THTMLCOL, COL_SM_GAINSBORO }, + { "ghostwhite", THTMLCOL, COL_SM_GHOSTWHITE }, + { "gold", THTMLCOL, COL_SM_GOLD }, + { "goldenrod", THTMLCOL, COL_SM_GOLDENROD }, + { "gray", TMATHMLCOL, COL_SM_GRAY }, + { "green", TMATHMLCOL, COL_SM_GREEN }, + { "greenyellow", THTMLCOL, COL_SM_GREENYELLOW }, + { "grey", THTMLCOL, COL_SM_GREY }, + { "honeydew", THTMLCOL, COL_SM_HONEYDEW }, + { "hotpink", THTMLCOL, COL_SM_HOTPINK }, + { "indianred", THTMLCOL, COL_SM_INDIANRED }, + { "indigo", THTMLCOL, COL_SM_INDIGO }, + { "ivory", THTMLCOL, COL_SM_IVORY }, + { "khaki", THTMLCOL, COL_SM_KHAKI }, + { "lavender", THTMLCOL, COL_SM_LAVENDER }, + { "lavenderblush", THTMLCOL, COL_SM_LAVENDERBLUSH }, + { "lawngreen", THTMLCOL, COL_SM_LAWNGREEN }, + { "lemonchiffon", THTMLCOL, COL_SM_LEMONCHIFFON }, + { "lightblue", THTMLCOL, COL_SM_LIGHTBLUE }, + { "lightcoral", THTMLCOL, COL_SM_LIGHTCORAL }, + { "lightcyan", THTMLCOL, COL_SM_LIGHTCYAN }, + { "lightgoldenrodyellow", THTMLCOL, COL_SM_LIGHTGOLDENRODYELLOW }, + { "lightgray", THTMLCOL, COL_SM_LIGHTGRAY }, + { "lightgreen", THTMLCOL, COL_SM_LIGHTGREEN }, + { "lightgrey", THTMLCOL, COL_SM_LIGHTGREY }, + { "lightpink", THTMLCOL, COL_SM_LIGHTPINK }, + { "lightsalmon", THTMLCOL, COL_SM_LIGHTSALMON }, + { "lightseagreen", THTMLCOL, COL_SM_LIGHTSEAGREEN }, + { "lightskyblue", THTMLCOL, COL_SM_LIGHTSKYBLUE }, + { "lightslategray", THTMLCOL, COL_SM_LIGHTSLATEGRAY }, + { "lightslategrey", THTMLCOL, COL_SM_LIGHTSLATEGREY }, + { "lightsteelblue", THTMLCOL, COL_SM_LIGHTSTEELBLUE }, + { "lightyellow", THTMLCOL, COL_SM_LIGHTYELLOW }, + { "lime", TMATHMLCOL, COL_SM_LIME }, + { "limegreen", THTMLCOL, COL_SM_LIMEGREEN }, + { "linen", THTMLCOL, COL_SM_LINEN }, + { "magenta", TMATHMLCOL, COL_SM_MAGENTA }, + { "maroon", TMATHMLCOL, COL_SM_MAROON }, + { "mediumaquamarine", THTMLCOL, COL_SM_MEDIUMAQUAMARINE }, + { "mediumblue", THTMLCOL, COL_SM_MEDIUMBLUE }, + { "mediumorchid", THTMLCOL, COL_SM_MEDIUMORCHID }, + { "mediumpurple", THTMLCOL, COL_SM_MEDIUMPURPLE }, + { "mediumseagreen", THTMLCOL, COL_SM_MEDIUMSEAGREEN }, + { "mediumslateblue", THTMLCOL, COL_SM_MEDIUMSLATEBLUE }, + { "mediumspringgreen", THTMLCOL, COL_SM_MEDIUMSPRINGGREEN }, + { "mediumturquoise", THTMLCOL, COL_SM_MEDIUMTURQUOISE }, + { "mediumvioletred", THTMLCOL, COL_SM_MEDIUMVIOLETRED }, + { "midnightblue", THTMLCOL, COL_SM_MIDNIGHTBLUE }, + { "mintcream", THTMLCOL, COL_SM_MINTCREAM }, + { "mistyrose", THTMLCOL, COL_SM_MISTYROSE }, + { "moccasin", THTMLCOL, COL_SM_MOCCASIN }, + { "navajowhite", THTMLCOL, COL_SM_NAVAJOWHITE }, + { "navy", TMATHMLCOL, COL_SM_NAVY }, + { "oldlace", THTMLCOL, COL_SM_OLDLACE }, + { "olive", TMATHMLCOL, COL_SM_OLIVE }, + { "olivedrab", THTMLCOL, COL_SM_OLIVEDRAB }, + { "orange", THTMLCOL, COL_SM_ORANGE }, + { "orangered", THTMLCOL, COL_SM_ORANGERED }, + { "orchid", THTMLCOL, COL_SM_ORCHID }, + { "palegoldenrod", THTMLCOL, COL_SM_PALEGOLDENROD }, + { "palegreen", THTMLCOL, COL_SM_PALEGREEN }, + { "paleturquoise", THTMLCOL, COL_SM_PALETURQUOISE }, + { "palevioletred", THTMLCOL, COL_SM_PALEVIOLETRED }, + { "papayawhip", THTMLCOL, COL_SM_PAPAYAWHIP }, + { "peachpuff", THTMLCOL, COL_SM_PEACHPUFF }, + { "peru", THTMLCOL, COL_SM_PERU }, + { "pink", THTMLCOL, COL_SM_PINK }, + { "plum", THTMLCOL, COL_SM_PLUM }, + { "powderblue", THTMLCOL, COL_SM_POWDERBLUE }, + { "purple", TMATHMLCOL, COL_SM_PURPLE }, + { "rebeccapurple", THTMLCOL, COL_SM_REBECCAPURPLE }, + { "red", TMATHMLCOL, COL_SM_RED }, + { "rosybrown", THTMLCOL, COL_SM_ROSYBROWN }, + { "royalblue", THTMLCOL, COL_SM_ROYALBLUE }, + { "saddlebrown", THTMLCOL, COL_SM_SADDLEBROWN }, + { "salmon", THTMLCOL, COL_SM_SALMON }, + { "sandybrown", THTMLCOL, COL_SM_SANDYBROWN }, + { "seagreen", THTMLCOL, COL_SM_SEAGREEN }, + { "seashell", THTMLCOL, COL_SM_SEASHELL }, + { "sienna", THTMLCOL, COL_SM_SIENNA }, + { "silver", TMATHMLCOL, COL_SM_SILVER }, + { "skyblue", THTMLCOL, COL_SM_SKYBLUE }, + { "slateblue", THTMLCOL, COL_SM_SLATEBLUE }, + { "slategray", THTMLCOL, COL_SM_SLATEGRAY }, + { "slategrey", THTMLCOL, COL_SM_SLATEGREY }, + { "snow", THTMLCOL, COL_SM_SNOW }, + { "springgreen", THTMLCOL, COL_SM_SPRINGGREEN }, + { "steelblue", THTMLCOL, COL_SM_STEELBLUE }, + { "tan", THTMLCOL, COL_SM_TAN }, + { "teal", TMATHMLCOL, COL_SM_TEAL }, + { "thistle", THTMLCOL, COL_SM_THISTLE }, + { "tomato", THTMLCOL, COL_SM_TOMATO }, + { "turquoise", THTMLCOL, COL_SM_TURQUOISE }, + { "violet", THTMLCOL, COL_SM_VIOLET }, + { "wheat", THTMLCOL, COL_SM_WHEAT }, + { "white", TMATHMLCOL, COL_SM_WHITE }, + { "whitesmoke", THTMLCOL, COL_SM_WHITESMOKE }, + { "yellow", TMATHMLCOL, COL_SM_YELLOW }, + { "yellowgreen", THTMLCOL, COL_SM_YELLOWGREEN } }; + +const SmColorTokenTableEntry starmathdatabase::aColorTokenTableDVIPS[] + = { { "apricot", TDVIPSNAMESCOL, COL_SM_DIV_APRICOT }, + { "aquamarine", TDVIPSNAMESCOL, COL_SM_DIV_AQUAMARINE }, + { "bittersweet", TDVIPSNAMESCOL, COL_SM_DIV_BITTERSWEET }, + { "black", TDVIPSNAMESCOL, COL_SM_BLACK }, + { "blue", TDVIPSNAMESCOL, COL_SM_BLACK } }; + +const SmColorTokenTableEntry starmathdatabase::aColorTokenTableMATHML[] = { + // clang-format off + { "aqua", TMATHMLCOL, COL_SM_AQUA }, + { "black", TMATHMLCOL, COL_SM_BLACK }, + { "blue", TMATHMLCOL, COL_SM_BLUE }, + { "fuchsia", TMATHMLCOL, COL_SM_FUCHSIA }, + { "gray", TMATHMLCOL, COL_SM_GRAY }, + { "green", TMATHMLCOL, COL_SM_GREEN }, + { "lime", TMATHMLCOL, COL_SM_LIME }, + { "maroon", TMATHMLCOL, COL_SM_MAROON }, + { "navy", TMATHMLCOL, COL_SM_NAVY }, + { "olive", TMATHMLCOL, COL_SM_OLIVE }, + { "purple", TMATHMLCOL, COL_SM_PURPLE }, + { "red", TMATHMLCOL, COL_SM_RED }, + { "silver", TMATHMLCOL, COL_SM_SILVER }, + { "teal", TMATHMLCOL, COL_SM_TEAL }, + { "white", TMATHMLCOL, COL_SM_WHITE }, + { "yellow", TMATHMLCOL, COL_SM_YELLOW } + // clang-format on +}; + +const SmColorTokenTableEntry starmathdatabase::aColorTokenTableERROR[] + = { { "", TERROR, COL_SM_BLACK } }; + +SmColorTokenTableEntry starmathdatabase::Identify_Color_Parser(sal_uInt32 cColor) +{ + for (auto i = std::begin(aColorTokenTableParse); i < std::end(aColorTokenTableParse); ++i) + if (i->equals(cColor)) + return i; + for (auto i = std::begin(aColorTokenTableDVIPS); i < std::end(aColorTokenTableDVIPS); ++i) + if (i->equals(cColor)) + return i; + if ((cColor & 0x00FFFFFF) == cColor) + return SmColorTokenTableEntry("", TRGB, cColor); + else + return SmColorTokenTableEntry("", TRGBA, cColor); +} + +SmColorTokenTableEntry starmathdatabase::Identify_Color_MATHML(sal_uInt32 cColor) +{ + for (auto i = std::begin(aColorTokenTableMATHML); i < std::end(aColorTokenTableMATHML); ++i) + if (i->equals(cColor)) + return i; + if ((cColor & 0x00FFFFFF) == cColor) + return SmColorTokenTableEntry("", TRGB, cColor); + else + return SmColorTokenTableEntry("", TRGBA, cColor); +} + +SmColorTokenTableEntry starmathdatabase::Identify_Color_DVIPSNAMES(sal_uInt32 cColor) +{ + for (auto i = std::begin(aColorTokenTableDVIPS); i < std::end(aColorTokenTableDVIPS); ++i) + if (i->equals(cColor)) + return i; + if ((cColor & 0x00FFFFFF) == cColor) + return SmColorTokenTableEntry("", TRGB, cColor); + else + return SmColorTokenTableEntry("", TRGBA, cColor); +} + +const SmColorTokenTableEntry* +starmathdatabase::Identify_ColorName_Parser(std::u16string_view colorname) +{ + if (colorname.empty()) + return &aColorTokenTableERROR[0]; + for (auto i = std::begin(aColorTokenTableParse); i < std::end(aColorTokenTableParse); ++i) + { + sal_Int32 matches = o3tl::compareToIgnoreAsciiCase(colorname, i->aIdent); + if (matches == 0) + return i; + if (matches < 0) + break; + } + return &aColorTokenTableERROR[0]; +} +SmColorTokenTableEntry starmathdatabase::Identify_ColorName_HTML(std::u16string_view colorname) +{ + if (colorname.empty()) + return SmColorTokenTableEntry("", TERROR, COL_SM_BLACK); + if (colorname[0] == '#') + { + Color col = Color::STRtoRGB(colorname); + return SmColorTokenTableEntry("", TRGB, col); + } + for (auto i = std::begin(aColorTokenTableHTML); i < std::end(aColorTokenTableHTML); ++i) + { + sal_Int32 matches = o3tl::compareToIgnoreAsciiCase(colorname, i->aIdent); + if (matches == 0) + return i; + if (matches < 0) + break; + } + return SmColorTokenTableEntry("", TERROR, COL_SM_BLACK); +} +const SmColorTokenTableEntry* +starmathdatabase::Identify_ColorName_DVIPSNAMES(std::u16string_view colorname) +{ + if (colorname.empty()) + return &aColorTokenTableERROR[0]; + for (auto i = std::begin(aColorTokenTableDVIPS); i < std::end(aColorTokenTableDVIPS); ++i) + { + sal_Int32 matches = o3tl::compareToIgnoreAsciiCase(colorname, i->aIdent); + if (matches == 0) + return i; + if (matches < 0) + break; + } + return &aColorTokenTableERROR[0]; +} diff --git a/starmath/source/mathml/xparsmlbase.cxx b/starmath/source/mathml/xparsmlbase.cxx new file mode 100644 index 0000000000..ccfcf0049e --- /dev/null +++ b/starmath/source/mathml/xparsmlbase.cxx @@ -0,0 +1,2166 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#include <xparsmlbase.hxx> + +static ::css::beans::Pair<::rtl::OUString, ::rtl::OUString> + icustomMathmlHtmlEntitiesData[starmathdatabase::STARMATH_MATHMLHTML_ENTITY_NUMBER] = { + // clang-format off + { u"AElig"_ustr, u"\u00C6"_ustr }, + { u"AMP"_ustr, u"\u0026"_ustr }, + { u"Aacute"_ustr, u"\u00C1"_ustr }, + { u"Abreve"_ustr, u"\u0102"_ustr }, + { u"Acirc"_ustr, u"\u00C2"_ustr }, + { u"Acy"_ustr, u"\u0410"_ustr }, + { u"Afr"_ustr, u"\U0001D504"_ustr }, + { u"Agrave"_ustr, u"\u00C0"_ustr }, + { u"Alpha"_ustr, u"\u0391"_ustr }, + { u"Amacr"_ustr, u"\u0100"_ustr }, + { u"And"_ustr, u"\u2A53"_ustr }, + { u"Aogon"_ustr, u"\u0104"_ustr }, + { u"Aopf"_ustr, u"\U0001D538"_ustr }, + { u"ApplyFunction"_ustr, u"\u2061"_ustr }, + { u"Aring"_ustr, u"\u00C5"_ustr }, + { u"Ascr"_ustr, u"\U0001D49C"_ustr }, + { u"Assign"_ustr, u"\u2254"_ustr }, + { u"Atilde"_ustr, u"\u00C3"_ustr }, + { u"Auml"_ustr, u"\u00C4"_ustr }, + { u"Backslash"_ustr, u"\u2216"_ustr }, + { u"Barv"_ustr, u"\u2AE7"_ustr }, + { u"Barwed"_ustr, u"\u2306"_ustr }, + { u"Bcy"_ustr, u"\u0411"_ustr }, + { u"Because"_ustr, u"\u2235"_ustr }, + { u"Bernoullis"_ustr, u"\u212C"_ustr }, + { u"Beta"_ustr, u"\u0392"_ustr }, + { u"Bfr"_ustr, u"\U0001D505"_ustr }, + { u"Bopf"_ustr, u"\U0001D539"_ustr }, + { u"Breve"_ustr, u"\u02D8"_ustr }, + { u"Bscr"_ustr, u"\u212C"_ustr }, + { u"Bumpeq"_ustr, u"\u224E"_ustr }, + { u"CHcy"_ustr, u"\u0427"_ustr }, + { u"COPY"_ustr, u"\u00A9"_ustr }, + { u"Cacute"_ustr, u"\u0106"_ustr }, + { u"Cap"_ustr, u"\u22D2"_ustr }, + { u"CapitalDifferentialD"_ustr, u"\u2145"_ustr }, + { u"Cayleys"_ustr, u"\u212D"_ustr }, + { u"Ccaron"_ustr, u"\u010C"_ustr }, + { u"Ccedil"_ustr, u"\u00C7"_ustr }, + { u"Ccirc"_ustr, u"\u0108"_ustr }, + { u"Cconint"_ustr, u"\u2230"_ustr }, + { u"Cdot"_ustr, u"\u010A"_ustr }, + { u"Cedilla"_ustr, u"\u00B8"_ustr }, + { u"CenterDot"_ustr, u"\u00B7"_ustr }, + { u"Cfr"_ustr, u"\u212D"_ustr }, + { u"Chi"_ustr, u"\u03A7"_ustr }, + { u"CircleDot"_ustr, u"\u2299"_ustr }, + { u"CircleMinus"_ustr, u"\u2296"_ustr }, + { u"CirclePlus"_ustr, u"\u2295"_ustr }, + { u"CircleTimes"_ustr, u"\u2297"_ustr }, + { u"ClockwiseContourIntegral"_ustr, u"\u2232"_ustr }, + { u"CloseCurlyDoubleQuote"_ustr, u"\u201D"_ustr }, + { u"CloseCurlyQuote"_ustr, u"\u2019"_ustr }, + { u"Colon"_ustr, u"\u2237"_ustr }, + { u"Colone"_ustr, u"\u2A74"_ustr }, + { u"Congruent"_ustr, u"\u2261"_ustr }, + { u"Conint"_ustr, u"\u222F"_ustr }, + { u"ContourIntegral"_ustr, u"\u222E"_ustr }, + { u"Copf"_ustr, u"\u2102"_ustr }, + { u"Coproduct"_ustr, u"\u2210"_ustr }, + { u"CounterClockwiseContourIntegral"_ustr, u"\u2233"_ustr }, + { u"Cross"_ustr, u"\u2A2F"_ustr }, + { u"Cscr"_ustr, u"\U0001D49E"_ustr }, + { u"Cup"_ustr, u"\u22D3"_ustr }, + { u"CupCap"_ustr, u"\u224D"_ustr }, + { u"DD"_ustr, u"\u2145"_ustr }, + { u"DDotrahd"_ustr, u"\u2911"_ustr }, + { u"DJcy"_ustr, u"\u0402"_ustr }, + { u"DScy"_ustr, u"\u0405"_ustr }, + { u"DZcy"_ustr, u"\u040F"_ustr }, + { u"Dagger"_ustr, u"\u2021"_ustr }, + { u"Darr"_ustr, u"\u21A1"_ustr }, + { u"Dashv"_ustr, u"\u2AE4"_ustr }, + { u"Dcaron"_ustr, u"\u010E"_ustr }, + { u"Dcy"_ustr, u"\u0414"_ustr }, + { u"Del"_ustr, u"\u2207"_ustr }, + { u"Delta"_ustr, u"\u0394"_ustr }, + { u"Dfr"_ustr, u"\U0001D507"_ustr }, + { u"DiacriticalAcute"_ustr, u"\u00B4"_ustr }, + { u"DiacriticalDot"_ustr, u"\u02D9"_ustr }, + { u"DiacriticalDoubleAcute"_ustr, u"\u02DD"_ustr }, + { u"DiacriticalGrave"_ustr, u"\u0060"_ustr }, + { u"DiacriticalTilde"_ustr, u"\u02DC"_ustr }, + { u"Diamond"_ustr, u"\u22C4"_ustr }, + { u"DifferentialD"_ustr, u"\u2146"_ustr }, + { u"Dopf"_ustr, u"\U0001D53B"_ustr }, + { u"Dot"_ustr, u"\u00A8"_ustr }, + { u"DotDot"_ustr, u"\u20DC"_ustr }, + { u"DotEqual"_ustr, u"\u2250"_ustr }, + { u"DoubleContourIntegral"_ustr, u"\u222F"_ustr }, + { u"DoubleDot"_ustr, u"\u00A8"_ustr }, + { u"DoubleDownArrow"_ustr, u"\u21D3"_ustr }, + { u"DoubleLeftArrow"_ustr, u"\u21D0"_ustr }, + { u"DoubleLeftRightArrow"_ustr, u"\u21D4"_ustr }, + { u"DoubleLeftTee"_ustr, u"\u2AE4"_ustr }, + { u"DoubleLongLeftArrow"_ustr, u"\u27F8"_ustr }, + { u"DoubleLongLeftRightArrow"_ustr, u"\u27FA"_ustr }, + { u"DoubleLongRightArrow"_ustr, u"\u27F9"_ustr }, + { u"DoubleRightArrow"_ustr, u"\u21D2"_ustr }, + { u"DoubleRightTee"_ustr, u"\u22A8"_ustr }, + { u"DoubleUpArrow"_ustr, u"\u21D1"_ustr }, + { u"DoubleUpDownArrow"_ustr, u"\u21D5"_ustr }, + { u"DoubleVerticalBar"_ustr, u"\u2225"_ustr }, + { u"DownArrow"_ustr, u"\u2193"_ustr }, + { u"DownArrowBar"_ustr, u"\u2913"_ustr }, + { u"DownArrowUpArrow"_ustr, u"\u21F5"_ustr }, + { u"DownBreve"_ustr, u"\u0311"_ustr }, + { u"DownLeftRightVector"_ustr, u"\u2950"_ustr }, + { u"DownLeftTeeVector"_ustr, u"\u295E"_ustr }, + { u"DownLeftVector"_ustr, u"\u21BD"_ustr }, + { u"DownLeftVectorBar"_ustr, u"\u2956"_ustr }, + { u"DownRightTeeVector"_ustr, u"\u295F"_ustr }, + { u"DownRightVector"_ustr, u"\u21C1"_ustr }, + { u"DownRightVectorBar"_ustr, u"\u2957"_ustr }, + { u"DownTee"_ustr, u"\u22A4"_ustr }, + { u"DownTeeArrow"_ustr, u"\u21A7"_ustr }, + { u"Downarrow"_ustr, u"\u21D3"_ustr }, + { u"Dscr"_ustr, u"\U0001D49F"_ustr }, + { u"Dstrok"_ustr, u"\u0110"_ustr }, + { u"ENG"_ustr, u"\u014A"_ustr }, + { u"ETH"_ustr, u"\u00D0"_ustr }, + { u"Eacute"_ustr, u"\u00C9"_ustr }, + { u"Ecaron"_ustr, u"\u011A"_ustr }, + { u"Ecirc"_ustr, u"\u00CA"_ustr }, + { u"Ecy"_ustr, u"\u042D"_ustr }, + { u"Edot"_ustr, u"\u0116"_ustr }, + { u"Efr"_ustr, u"\U0001D508"_ustr }, + { u"Egrave"_ustr, u"\u00C8"_ustr }, + { u"Element"_ustr, u"\u2208"_ustr }, + { u"Emacr"_ustr, u"\u0112"_ustr }, + { u"EmptySmallSquare"_ustr, u"\u25FB"_ustr }, + { u"EmptyVerySmallSquare"_ustr, u"\u25AB"_ustr }, + { u"Eogon"_ustr, u"\u0118"_ustr }, + { u"Eopf"_ustr, u"\U0001D53C"_ustr }, + { u"Epsilon"_ustr, u"\u0395"_ustr }, + { u"Equal"_ustr, u"\u2A75"_ustr }, + { u"EqualTilde"_ustr, u"\u2242"_ustr }, + { u"Equilibrium"_ustr, u"\u21CC"_ustr }, + { u"Escr"_ustr, u"\u2130"_ustr }, + { u"Esim"_ustr, u"\u2A73"_ustr }, + { u"Eta"_ustr, u"\u0397"_ustr }, + { u"Euml"_ustr, u"\u00CB"_ustr }, + { u"Exists"_ustr, u"\u2203"_ustr }, + { u"ExponentialE"_ustr, u"\u2147"_ustr }, + { u"Fcy"_ustr, u"\u0424"_ustr }, + { u"Ffr"_ustr, u"\U0001D509"_ustr }, + { u"FilledSmallSquare"_ustr, u"\u25FC"_ustr }, + { u"FilledVerySmallSquare"_ustr, u"\u25AA"_ustr }, + { u"Fopf"_ustr, u"\U0001D53D"_ustr }, + { u"ForAll"_ustr, u"\u2200"_ustr }, + { u"Fouriertrf"_ustr, u"\u2131"_ustr }, + { u"Fscr"_ustr, u"\u2131"_ustr }, + { u"GJcy"_ustr, u"\u0403"_ustr }, + { u"GT"_ustr, u"\u003E"_ustr }, + { u"Gamma"_ustr, u"\u0393"_ustr }, + { u"Gammad"_ustr, u"\u03DC"_ustr }, + { u"Gbreve"_ustr, u"\u011E"_ustr }, + { u"Gcedil"_ustr, u"\u0122"_ustr }, + { u"Gcirc"_ustr, u"\u011C"_ustr }, + { u"Gcy"_ustr, u"\u0413"_ustr }, + { u"Gdot"_ustr, u"\u0120"_ustr }, + { u"Gfr"_ustr, u"\U0001D50A"_ustr }, + { u"Gg"_ustr, u"\u22D9"_ustr }, + { u"Gopf"_ustr, u"\U0001D53E"_ustr }, + { u"GreaterEqual"_ustr, u"\u2265"_ustr }, + { u"GreaterEqualLess"_ustr, u"\u22DB"_ustr }, + { u"GreaterFullEqual"_ustr, u"\u2267"_ustr }, + { u"GreaterGreater"_ustr, u"\u2AA2"_ustr }, + { u"GreaterLess"_ustr, u"\u2277"_ustr }, + { u"GreaterSlantEqual"_ustr, u"\u2A7E"_ustr }, + { u"GreaterTilde"_ustr, u"\u2273"_ustr }, + { u"Gscr"_ustr, u"\U0001D4A2"_ustr }, + { u"Gt"_ustr, u"\u226B"_ustr }, + { u"HARDcy"_ustr, u"\u042A"_ustr }, + { u"Hacek"_ustr, u"\u02C7"_ustr }, + { u"Hat"_ustr, u"\u005E"_ustr }, + { u"Hcirc"_ustr, u"\u0124"_ustr }, + { u"Hfr"_ustr, u"\u210C"_ustr }, + { u"HilbertSpace"_ustr, u"\u210B"_ustr }, + { u"Hopf"_ustr, u"\u210D"_ustr }, + { u"HorizontalLine"_ustr, u"\u2500"_ustr }, + { u"Hscr"_ustr, u"\u210B"_ustr }, + { u"Hstrok"_ustr, u"\u0126"_ustr }, + { u"HumpDownHump"_ustr, u"\u224E"_ustr }, + { u"HumpEqual"_ustr, u"\u224F"_ustr }, + { u"IEcy"_ustr, u"\u0415"_ustr }, + { u"IJlig"_ustr, u"\u0132"_ustr }, + { u"IOcy"_ustr, u"\u0401"_ustr }, + { u"Iacute"_ustr, u"\u00CD"_ustr }, + { u"Icirc"_ustr, u"\u00CE"_ustr }, + { u"Icy"_ustr, u"\u0418"_ustr }, + { u"Idot"_ustr, u"\u0130"_ustr }, + { u"Ifr"_ustr, u"\u2111"_ustr }, + { u"Igrave"_ustr, u"\u00CC"_ustr }, + { u"Im"_ustr, u"\u2111"_ustr }, + { u"Imacr"_ustr, u"\u012A"_ustr }, + { u"ImaginaryI"_ustr, u"\u2148"_ustr }, + { u"Implies"_ustr, u"\u21D2"_ustr }, + { u"Int"_ustr, u"\u222C"_ustr }, + { u"Integral"_ustr, u"\u222B"_ustr }, + { u"Intersection"_ustr, u"\u22C2"_ustr }, + { u"InvisibleComma"_ustr, u"\u2063"_ustr }, + { u"InvisibleTimes"_ustr, u"\u2062"_ustr }, + { u"Iogon"_ustr, u"\u012E"_ustr }, + { u"Iopf"_ustr, u"\U0001D540"_ustr }, + { u"Iota"_ustr, u"\u0399"_ustr }, + { u"Iscr"_ustr, u"\u2110"_ustr }, + { u"Itilde"_ustr, u"\u0128"_ustr }, + { u"Iukcy"_ustr, u"\u0406"_ustr }, + { u"Iuml"_ustr, u"\u00CF"_ustr }, + { u"Jcirc"_ustr, u"\u0134"_ustr }, + { u"Jcy"_ustr, u"\u0419"_ustr }, + { u"Jfr"_ustr, u"\U0001D50D"_ustr }, + { u"Jopf"_ustr, u"\U0001D541"_ustr }, + { u"Jscr"_ustr, u"\U0001D4A5"_ustr }, + { u"Jsercy"_ustr, u"\u0408"_ustr }, + { u"Jukcy"_ustr, u"\u0404"_ustr }, + { u"KHcy"_ustr, u"\u0425"_ustr }, + { u"KJcy"_ustr, u"\u040C"_ustr }, + { u"Kappa"_ustr, u"\u039A"_ustr }, + { u"Kcedil"_ustr, u"\u0136"_ustr }, + { u"Kcy"_ustr, u"\u041A"_ustr }, + { u"Kfr"_ustr, u"\U0001D50E"_ustr }, + { u"Kopf"_ustr, u"\U0001D542"_ustr }, + { u"Kscr"_ustr, u"\U0001D4A6"_ustr }, + { u"LJcy"_ustr, u"\u0409"_ustr }, + { u"LT"_ustr, u"\u003C"_ustr }, + { u"Lacute"_ustr, u"\u0139"_ustr }, + { u"Lambda"_ustr, u"\u039B"_ustr }, + { u"Lang"_ustr, u"\u27EA"_ustr }, + { u"Laplacetrf"_ustr, u"\u2112"_ustr }, + { u"Larr"_ustr, u"\u219E"_ustr }, + { u"Lcaron"_ustr, u"\u013D"_ustr }, + { u"Lcedil"_ustr, u"\u013B"_ustr }, + { u"Lcy"_ustr, u"\u041B"_ustr }, + { u"LeftAngleBracket"_ustr, u"\u27E8"_ustr }, + { u"LeftArrow"_ustr, u"\u2190"_ustr }, + { u"LeftArrowBar"_ustr, u"\u21E4"_ustr }, + { u"LeftArrowRightArrow"_ustr, u"\u21C6"_ustr }, + { u"LeftCeiling"_ustr, u"\u2308"_ustr }, + { u"LeftDoubleBracket"_ustr, u"\u27E6"_ustr }, + { u"LeftDownTeeVector"_ustr, u"\u2961"_ustr }, + { u"LeftDownVector"_ustr, u"\u21C3"_ustr }, + { u"LeftDownVectorBar"_ustr, u"\u2959"_ustr }, + { u"LeftFloor"_ustr, u"\u230A"_ustr }, + { u"LeftRightArrow"_ustr, u"\u2194"_ustr }, + { u"LeftRightVector"_ustr, u"\u294E"_ustr }, + { u"LeftTee"_ustr, u"\u22A3"_ustr }, + { u"LeftTeeArrow"_ustr, u"\u21A4"_ustr }, + { u"LeftTeeVector"_ustr, u"\u295A"_ustr }, + { u"LeftTriangle"_ustr, u"\u22B2"_ustr }, + { u"LeftTriangleBar"_ustr, u"\u29CF"_ustr }, + { u"LeftTriangleEqual"_ustr, u"\u22B4"_ustr }, + { u"LeftUpDownVector"_ustr, u"\u2951"_ustr }, + { u"LeftUpTeeVector"_ustr, u"\u2960"_ustr }, + { u"LeftUpVector"_ustr, u"\u21BF"_ustr }, + { u"LeftUpVectorBar"_ustr, u"\u2958"_ustr }, + { u"LeftVector"_ustr, u"\u21BC"_ustr }, + { u"LeftVectorBar"_ustr, u"\u2952"_ustr }, + { u"Leftarrow"_ustr, u"\u21D0"_ustr }, + { u"Leftrightarrow"_ustr, u"\u21D4"_ustr }, + { u"LessEqualGreater"_ustr, u"\u22DA"_ustr }, + { u"LessFullEqual"_ustr, u"\u2266"_ustr }, + { u"LessGreater"_ustr, u"\u2276"_ustr }, + { u"LessLess"_ustr, u"\u2AA1"_ustr }, + { u"LessSlantEqual"_ustr, u"\u2A7D"_ustr }, + { u"LessTilde"_ustr, u"\u2272"_ustr }, + { u"Lfr"_ustr, u"\U0001D50F"_ustr }, + { u"Ll"_ustr, u"\u22D8"_ustr }, + { u"Lleftarrow"_ustr, u"\u21DA"_ustr }, + { u"Lmidot"_ustr, u"\u013F"_ustr }, + { u"LongLeftArrow"_ustr, u"\u27F5"_ustr }, + { u"LongLeftRightArrow"_ustr, u"\u27F7"_ustr }, + { u"LongRightArrow"_ustr, u"\u27F6"_ustr }, + { u"Longleftarrow"_ustr, u"\u27F8"_ustr }, + { u"Longleftrightarrow"_ustr, u"\u27FA"_ustr }, + { u"Longrightarrow"_ustr, u"\u27F9"_ustr }, + { u"Lopf"_ustr, u"\U0001D543"_ustr }, + { u"LowerLeftArrow"_ustr, u"\u2199"_ustr }, + { u"LowerRightArrow"_ustr, u"\u2198"_ustr }, + { u"Lscr"_ustr, u"\u2112"_ustr }, + { u"Lsh"_ustr, u"\u21B0"_ustr }, + { u"Lstrok"_ustr, u"\u0141"_ustr }, + { u"Lt"_ustr, u"\u226A"_ustr }, + { u"Map"_ustr, u"\u2905"_ustr }, + { u"Mcy"_ustr, u"\u041C"_ustr }, + { u"MediumSpace"_ustr, u"\u205F"_ustr }, + { u"Mellintrf"_ustr, u"\u2133"_ustr }, + { u"Mfr"_ustr, u"\U0001D510"_ustr }, + { u"MinusPlus"_ustr, u"\u2213"_ustr }, + { u"Mopf"_ustr, u"\U0001D544"_ustr }, + { u"Mscr"_ustr, u"\u2133"_ustr }, + { u"Mu"_ustr, u"\u039C"_ustr }, + { u"NJcy"_ustr, u"\u040A"_ustr }, + { u"Nacute"_ustr, u"\u0143"_ustr }, + { u"Ncaron"_ustr, u"\u0147"_ustr }, + { u"Ncedil"_ustr, u"\u0145"_ustr }, + { u"Ncy"_ustr, u"\u041D"_ustr }, + { u"NegativeMediumSpace"_ustr, u"\u200B"_ustr }, + { u"NegativeThickSpace"_ustr, u"\u200B"_ustr }, + { u"NegativeThinSpace"_ustr, u"\u200B"_ustr }, + { u"NegativeVeryThinSpace"_ustr, u"\u200B"_ustr }, + { u"NestedGreaterGreater"_ustr, u"\u226B"_ustr }, + { u"NestedLessLess"_ustr, u"\u226A"_ustr }, + { u"NewLine"_ustr, u"\u000A"_ustr }, + { u"Nfr"_ustr, u"\U0001D511"_ustr }, + { u"NoBreak"_ustr, u"\u2060"_ustr }, + { u"NonBreakingSpace"_ustr, u"\u00A0"_ustr }, + { u"Nopf"_ustr, u"\u2115"_ustr }, + { u"Not"_ustr, u"\u2AEC"_ustr }, + { u"NotCongruent"_ustr, u"\u2262"_ustr }, + { u"NotCupCap"_ustr, u"\u226D"_ustr }, + { u"NotDoubleVerticalBar"_ustr, u"\u2226"_ustr }, + { u"NotElement"_ustr, u"\u2209"_ustr }, + { u"NotEqual"_ustr, u"\u2260"_ustr }, + { u"NotEqualTilde"_ustr, u"\u2242\u0338"_ustr }, + { u"NotExists"_ustr, u"\u2204"_ustr }, + { u"NotGreater"_ustr, u"\u226F"_ustr }, + { u"NotGreaterEqual"_ustr, u"\u2271"_ustr }, + { u"NotGreaterFullEqual"_ustr, u"\u2267\u0338"_ustr }, + { u"NotGreaterGreater"_ustr, u"\u226B\u0338"_ustr }, + { u"NotGreaterLess"_ustr, u"\u2279"_ustr }, + { u"NotGreaterSlantEqual"_ustr, u"\u2A7E\u0338"_ustr }, + { u"NotGreaterTilde"_ustr, u"\u2275"_ustr }, + { u"NotHumpDownHump"_ustr, u"\u224E\u0338"_ustr }, + { u"NotHumpEqual"_ustr, u"\u224F\u0338"_ustr }, + { u"NotLeftTriangle"_ustr, u"\u22EA"_ustr }, + { u"NotLeftTriangleBar"_ustr, u"\u29CF\u0338"_ustr }, + { u"NotLeftTriangleEqual"_ustr, u"\u22EC"_ustr }, + { u"NotLess"_ustr, u"\u226E"_ustr }, + { u"NotLessEqual"_ustr, u"\u2270"_ustr }, + { u"NotLessGreater"_ustr, u"\u2278"_ustr }, + { u"NotLessLess"_ustr, u"\u226A\u0338"_ustr }, + { u"NotLessSlantEqual"_ustr, u"\u2A7D\u0338"_ustr }, + { u"NotLessTilde"_ustr, u"\u2274"_ustr }, + { u"NotNestedGreaterGreater"_ustr, u"\u2AA2\u0338"_ustr }, + { u"NotNestedLessLess"_ustr, u"\u2AA1\u0338"_ustr }, + { u"NotPrecedes"_ustr, u"\u2280"_ustr }, + { u"NotPrecedesEqual"_ustr, u"\u2AAF\u0338"_ustr }, + { u"NotPrecedesSlantEqual"_ustr, u"\u22E0"_ustr }, + { u"NotReverseElement"_ustr, u"\u220C"_ustr }, + { u"NotRightTriangle"_ustr, u"\u22EB"_ustr }, + { u"NotRightTriangleBar"_ustr, u"\u29D0\u0338"_ustr }, + { u"NotRightTriangleEqual"_ustr, u"\u22ED"_ustr }, + { u"NotSquareSubset"_ustr, u"\u228F\u0338"_ustr }, + { u"NotSquareSubsetEqual"_ustr, u"\u22E2"_ustr }, + { u"NotSquareSuperset"_ustr, u"\u2290\u0338"_ustr }, + { u"NotSquareSupersetEqual"_ustr, u"\u22E3"_ustr }, + { u"NotSubset"_ustr, u"\u2282\u20D2"_ustr }, + { u"NotSubsetEqual"_ustr, u"\u2288"_ustr }, + { u"NotSucceeds"_ustr, u"\u2281"_ustr }, + { u"NotSucceedsEqual"_ustr, u"\u2AB0\u0338"_ustr }, + { u"NotSucceedsSlantEqual"_ustr, u"\u22E1"_ustr }, + { u"NotSucceedsTilde"_ustr, u"\u227F\u0338"_ustr }, + { u"NotSuperset"_ustr, u"\u2283\u20D2"_ustr }, + { u"NotSupersetEqual"_ustr, u"\u2289"_ustr }, + { u"NotTilde"_ustr, u"\u2241"_ustr }, + { u"NotTildeEqual"_ustr, u"\u2244"_ustr }, + { u"NotTildeFullEqual"_ustr, u"\u2247"_ustr }, + { u"NotTildeTilde"_ustr, u"\u2249"_ustr }, + { u"NotVerticalBar"_ustr, u"\u2224"_ustr }, + { u"Nscr"_ustr, u"\U0001D4A9"_ustr }, + { u"Ntilde"_ustr, u"\u00D1"_ustr }, + { u"Nu"_ustr, u"\u039D"_ustr }, + { u"OElig"_ustr, u"\u0152"_ustr }, + { u"Oacute"_ustr, u"\u00D3"_ustr }, + { u"Ocirc"_ustr, u"\u00D4"_ustr }, + { u"Ocy"_ustr, u"\u041E"_ustr }, + { u"Odblac"_ustr, u"\u0150"_ustr }, + { u"Ofr"_ustr, u"\U0001D512"_ustr }, + { u"Ograve"_ustr, u"\u00D2"_ustr }, + { u"Omacr"_ustr, u"\u014C"_ustr }, + { u"Omega"_ustr, u"\u03A9"_ustr }, + { u"Omicron"_ustr, u"\u039F"_ustr }, + { u"Oopf"_ustr, u"\U0001D546"_ustr }, + { u"OpenCurlyDoubleQuote"_ustr, u"\u201C"_ustr }, + { u"OpenCurlyQuote"_ustr, u"\u2018"_ustr }, + { u"Or"_ustr, u"\u2A54"_ustr }, + { u"Oscr"_ustr, u"\U0001D4AA"_ustr }, + { u"Oslash"_ustr, u"\u00D8"_ustr }, + { u"Otilde"_ustr, u"\u00D5"_ustr }, + { u"Otimes"_ustr, u"\u2A37"_ustr }, + { u"Ouml"_ustr, u"\u00D6"_ustr }, + { u"OverBar"_ustr, u"\u203E"_ustr }, + { u"OverBrace"_ustr, u"\u23DE"_ustr }, + { u"OverBracket"_ustr, u"\u23B4"_ustr }, + { u"OverParenthesis"_ustr, u"\u23DC"_ustr }, + { u"PartialD"_ustr, u"\u2202"_ustr }, + { u"Pcy"_ustr, u"\u041F"_ustr }, + { u"Pfr"_ustr, u"\U0001D513"_ustr }, + { u"Phi"_ustr, u"\u03A6"_ustr }, + { u"Pi"_ustr, u"\u03A0"_ustr }, + { u"PlusMinus"_ustr, u"\u00B1"_ustr }, + { u"Poincareplane"_ustr, u"\u210C"_ustr }, + { u"Popf"_ustr, u"\u2119"_ustr }, + { u"Pr"_ustr, u"\u2ABB"_ustr }, + { u"Precedes"_ustr, u"\u227A"_ustr }, + { u"PrecedesEqual"_ustr, u"\u2AAF"_ustr }, + { u"PrecedesSlantEqual"_ustr, u"\u227C"_ustr }, + { u"PrecedesTilde"_ustr, u"\u227E"_ustr }, + { u"Prime"_ustr, u"\u2033"_ustr }, + { u"Product"_ustr, u"\u220F"_ustr }, + { u"Proportion"_ustr, u"\u2237"_ustr }, + { u"Proportional"_ustr, u"\u221D"_ustr }, + { u"Pscr"_ustr, u"\U0001D4AB"_ustr }, + { u"Psi"_ustr, u"\u03A8"_ustr }, + { u"QUOT"_ustr, u"\u0022"_ustr }, + { u"Qfr"_ustr, u"\U0001D514"_ustr }, + { u"Qopf"_ustr, u"\u211A"_ustr }, + { u"Qscr"_ustr, u"\U0001D4AC"_ustr }, + { u"RBarr"_ustr, u"\u2910"_ustr }, + { u"REG"_ustr, u"\u00AE"_ustr }, + { u"Racute"_ustr, u"\u0154"_ustr }, + { u"Rang"_ustr, u"\u27EB"_ustr }, + { u"Rarr"_ustr, u"\u21A0"_ustr }, + { u"Rarrtl"_ustr, u"\u2916"_ustr }, + { u"Rcaron"_ustr, u"\u0158"_ustr }, + { u"Rcedil"_ustr, u"\u0156"_ustr }, + { u"Rcy"_ustr, u"\u0420"_ustr }, + { u"Re"_ustr, u"\u211C"_ustr }, + { u"ReverseElement"_ustr, u"\u220B"_ustr }, + { u"ReverseEquilibrium"_ustr, u"\u21CB"_ustr }, + { u"ReverseUpEquilibrium"_ustr, u"\u296F"_ustr }, + { u"Rfr"_ustr, u"\u211C"_ustr }, + { u"Rho"_ustr, u"\u03A1"_ustr }, + { u"RightAngleBracket"_ustr, u"\u27E9"_ustr }, + { u"RightArrow"_ustr, u"\u2192"_ustr }, + { u"RightArrowBar"_ustr, u"\u21E5"_ustr }, + { u"RightArrowLeftArrow"_ustr, u"\u21C4"_ustr }, + { u"RightCeiling"_ustr, u"\u2309"_ustr }, + { u"RightDoubleBracket"_ustr, u"\u27E7"_ustr }, + { u"RightDownTeeVector"_ustr, u"\u295D"_ustr }, + { u"RightDownVector"_ustr, u"\u21C2"_ustr }, + { u"RightDownVectorBar"_ustr, u"\u2955"_ustr }, + { u"RightFloor"_ustr, u"\u230B"_ustr }, + { u"RightTee"_ustr, u"\u22A2"_ustr }, + { u"RightTeeArrow"_ustr, u"\u21A6"_ustr }, + { u"RightTeeVector"_ustr, u"\u295B"_ustr }, + { u"RightTriangle"_ustr, u"\u22B3"_ustr }, + { u"RightTriangleBar"_ustr, u"\u29D0"_ustr }, + { u"RightTriangleEqual"_ustr, u"\u22B5"_ustr }, + { u"RightUpDownVector"_ustr, u"\u294F"_ustr }, + { u"RightUpTeeVector"_ustr, u"\u295C"_ustr }, + { u"RightUpVector"_ustr, u"\u21BE"_ustr }, + { u"RightUpVectorBar"_ustr, u"\u2954"_ustr }, + { u"RightVector"_ustr, u"\u21C0"_ustr }, + { u"RightVectorBar"_ustr, u"\u2953"_ustr }, + { u"Rightarrow"_ustr, u"\u21D2"_ustr }, + { u"Ropf"_ustr, u"\u211D"_ustr }, + { u"RoundImplies"_ustr, u"\u2970"_ustr }, + { u"Rrightarrow"_ustr, u"\u21DB"_ustr }, + { u"Rscr"_ustr, u"\u211B"_ustr }, + { u"Rsh"_ustr, u"\u21B1"_ustr }, + { u"RuleDelayed"_ustr, u"\u29F4"_ustr }, + { u"SHCHcy"_ustr, u"\u0429"_ustr }, + { u"SHcy"_ustr, u"\u0428"_ustr }, + { u"SOFTcy"_ustr, u"\u042C"_ustr }, + { u"Sacute"_ustr, u"\u015A"_ustr }, + { u"Sc"_ustr, u"\u2ABC"_ustr }, + { u"Scaron"_ustr, u"\u0160"_ustr }, + { u"Scedil"_ustr, u"\u015E"_ustr }, + { u"Scirc"_ustr, u"\u015C"_ustr }, + { u"Scy"_ustr, u"\u0421"_ustr }, + { u"Sfr"_ustr, u"\U0001D516"_ustr }, + { u"ShortDownArrow"_ustr, u"\u2193"_ustr }, + { u"ShortLeftArrow"_ustr, u"\u2190"_ustr }, + { u"ShortRightArrow"_ustr, u"\u2192"_ustr }, + { u"ShortUpArrow"_ustr, u"\u2191"_ustr }, + { u"Sigma"_ustr, u"\u03A3"_ustr }, + { u"SmallCircle"_ustr, u"\u2218"_ustr }, + { u"Sopf"_ustr, u"\U0001D54A"_ustr }, + { u"Sqrt"_ustr, u"\u221A"_ustr }, + { u"Square"_ustr, u"\u25A1"_ustr }, + { u"SquareIntersection"_ustr, u"\u2293"_ustr }, + { u"SquareSubset"_ustr, u"\u228F"_ustr }, + { u"SquareSubsetEqual"_ustr, u"\u2291"_ustr }, + { u"SquareSuperset"_ustr, u"\u2290"_ustr }, + { u"SquareSupersetEqual"_ustr, u"\u2292"_ustr }, + { u"SquareUnion"_ustr, u"\u2294"_ustr }, + { u"Sscr"_ustr, u"\U0001D4AE"_ustr }, + { u"Star"_ustr, u"\u22C6"_ustr }, + { u"Sub"_ustr, u"\u22D0"_ustr }, + { u"Subset"_ustr, u"\u22D0"_ustr }, + { u"SubsetEqual"_ustr, u"\u2286"_ustr }, + { u"Succeeds"_ustr, u"\u227B"_ustr }, + { u"SucceedsEqual"_ustr, u"\u2AB0"_ustr }, + { u"SucceedsSlantEqual"_ustr, u"\u227D"_ustr }, + { u"SucceedsTilde"_ustr, u"\u227F"_ustr }, + { u"SuchThat"_ustr, u"\u220B"_ustr }, + { u"Sum"_ustr, u"\u2211"_ustr }, + { u"Sup"_ustr, u"\u22D1"_ustr }, + { u"Superset"_ustr, u"\u2283"_ustr }, + { u"SupersetEqual"_ustr, u"\u2287"_ustr }, + { u"Supset"_ustr, u"\u22D1"_ustr }, + { u"THORN"_ustr, u"\u00DE"_ustr }, + { u"TRADE"_ustr, u"\u2122"_ustr }, + { u"TSHcy"_ustr, u"\u040B"_ustr }, + { u"TScy"_ustr, u"\u0426"_ustr }, + { u"Tab"_ustr, u"\u0009"_ustr }, + { u"Tau"_ustr, u"\u03A4"_ustr }, + { u"Tcaron"_ustr, u"\u0164"_ustr }, + { u"Tcedil"_ustr, u"\u0162"_ustr }, + { u"Tcy"_ustr, u"\u0422"_ustr }, + { u"Tfr"_ustr, u"\U0001D517"_ustr }, + { u"Therefore"_ustr, u"\u2234"_ustr }, + { u"Theta"_ustr, u"\u0398"_ustr }, + { u"ThickSpace"_ustr, u"\u205F\u200A"_ustr }, + { u"ThinSpace"_ustr, u"\u2009"_ustr }, + { u"Tilde"_ustr, u"\u223C"_ustr }, + { u"TildeEqual"_ustr, u"\u2243"_ustr }, + { u"TildeFullEqual"_ustr, u"\u2245"_ustr }, + { u"TildeTilde"_ustr, u"\u2248"_ustr }, + { u"Topf"_ustr, u"\U0001D54B"_ustr }, + { u"TripleDot"_ustr, u"\u20DB"_ustr }, + { u"Tscr"_ustr, u"\U0001D4AF"_ustr }, + { u"Tstrok"_ustr, u"\u0166"_ustr }, + { u"Uacute"_ustr, u"\u00DA"_ustr }, + { u"Uarr"_ustr, u"\u219F"_ustr }, + { u"Uarrocir"_ustr, u"\u2949"_ustr }, + { u"Ubrcy"_ustr, u"\u040E"_ustr }, + { u"Ubreve"_ustr, u"\u016C"_ustr }, + { u"Ucirc"_ustr, u"\u00DB"_ustr }, + { u"Ucy"_ustr, u"\u0423"_ustr }, + { u"Udblac"_ustr, u"\u0170"_ustr }, + { u"Ufr"_ustr, u"\U0001D518"_ustr }, + { u"Ugrave"_ustr, u"\u00D9"_ustr }, + { u"Umacr"_ustr, u"\u016A"_ustr }, + { u"UnderBar"_ustr, u"\u005F"_ustr }, + { u"UnderBrace"_ustr, u"\u23DF"_ustr }, + { u"UnderBracket"_ustr, u"\u23B5"_ustr }, + { u"UnderParenthesis"_ustr, u"\u23DD"_ustr }, + { u"Union"_ustr, u"\u22C3"_ustr }, + { u"UnionPlus"_ustr, u"\u228E"_ustr }, + { u"Uogon"_ustr, u"\u0172"_ustr }, + { u"Uopf"_ustr, u"\U0001D54C"_ustr }, + { u"UpArrow"_ustr, u"\u2191"_ustr }, + { u"UpArrowBar"_ustr, u"\u2912"_ustr }, + { u"UpArrowDownArrow"_ustr, u"\u21C5"_ustr }, + { u"UpDownArrow"_ustr, u"\u2195"_ustr }, + { u"UpEquilibrium"_ustr, u"\u296E"_ustr }, + { u"UpTee"_ustr, u"\u22A5"_ustr }, + { u"UpTeeArrow"_ustr, u"\u21A5"_ustr }, + { u"Uparrow"_ustr, u"\u21D1"_ustr }, + { u"Updownarrow"_ustr, u"\u21D5"_ustr }, + { u"UpperLeftArrow"_ustr, u"\u2196"_ustr }, + { u"UpperRightArrow"_ustr, u"\u2197"_ustr }, + { u"Upsi"_ustr, u"\u03D2"_ustr }, + { u"Upsilon"_ustr, u"\u03A5"_ustr }, + { u"Uring"_ustr, u"\u016E"_ustr }, + { u"Uscr"_ustr, u"\U0001D4B0"_ustr }, + { u"Utilde"_ustr, u"\u0168"_ustr }, + { u"Uuml"_ustr, u"\u00DC"_ustr }, + { u"VDash"_ustr, u"\u22AB"_ustr }, + { u"Vbar"_ustr, u"\u2AEB"_ustr }, + { u"Vcy"_ustr, u"\u0412"_ustr }, + { u"Vdash"_ustr, u"\u22A9"_ustr }, + { u"Vdashl"_ustr, u"\u2AE6"_ustr }, + { u"Vee"_ustr, u"\u22C1"_ustr }, + { u"Verbar"_ustr, u"\u2016"_ustr }, + { u"Vert"_ustr, u"\u2016"_ustr }, + { u"VerticalBar"_ustr, u"\u2223"_ustr }, + { u"VerticalLine"_ustr, u"\u007C"_ustr }, + { u"VerticalSeparator"_ustr, u"\u2758"_ustr }, + { u"VerticalTilde"_ustr, u"\u2240"_ustr }, + { u"VeryThinSpace"_ustr, u"\u200A"_ustr }, + { u"Vfr"_ustr, u"\U0001D519"_ustr }, + { u"Vopf"_ustr, u"\U0001D54D"_ustr }, + { u"Vscr"_ustr, u"\U0001D4B1"_ustr }, + { u"Vvdash"_ustr, u"\u22AA"_ustr }, + { u"Wcirc"_ustr, u"\u0174"_ustr }, + { u"Wedge"_ustr, u"\u22C0"_ustr }, + { u"Wfr"_ustr, u"\U0001D51A"_ustr }, + { u"Wopf"_ustr, u"\U0001D54E"_ustr }, + { u"Wscr"_ustr, u"\U0001D4B2"_ustr }, + { u"Xfr"_ustr, u"\U0001D51B"_ustr }, + { u"Xi"_ustr, u"\u039E"_ustr }, + { u"Xopf"_ustr, u"\U0001D54F"_ustr }, + { u"Xscr"_ustr, u"\U0001D4B3"_ustr }, + { u"YAcy"_ustr, u"\u042F"_ustr }, + { u"YIcy"_ustr, u"\u0407"_ustr }, + { u"YUcy"_ustr, u"\u042E"_ustr }, + { u"Yacute"_ustr, u"\u00DD"_ustr }, + { u"Ycirc"_ustr, u"\u0176"_ustr }, + { u"Ycy"_ustr, u"\u042B"_ustr }, + { u"Yfr"_ustr, u"\U0001D51C"_ustr }, + { u"Yopf"_ustr, u"\U0001D550"_ustr }, + { u"Yscr"_ustr, u"\U0001D4B4"_ustr }, + { u"Yuml"_ustr, u"\u0178"_ustr }, + { u"ZHcy"_ustr, u"\u0416"_ustr }, + { u"Zacute"_ustr, u"\u0179"_ustr }, + { u"Zcaron"_ustr, u"\u017D"_ustr }, + { u"Zcy"_ustr, u"\u0417"_ustr }, + { u"Zdot"_ustr, u"\u017B"_ustr }, + { u"ZeroWidthSpace"_ustr, u"\u200B"_ustr }, + { u"Zeta"_ustr, u"\u0396"_ustr }, + { u"Zfr"_ustr, u"\u2128"_ustr }, + { u"Zopf"_ustr, u"\u2124"_ustr }, + { u"Zscr"_ustr, u"\U0001D4B5"_ustr }, + { u"aacute"_ustr, u"\u00E1"_ustr }, + { u"abreve"_ustr, u"\u0103"_ustr }, + { u"ac"_ustr, u"\u223E"_ustr }, + { u"acE"_ustr, u"\u223E\u0333"_ustr }, + { u"acd"_ustr, u"\u223F"_ustr }, + { u"acirc"_ustr, u"\u00E2"_ustr }, + { u"acute"_ustr, u"\u00B4"_ustr }, + { u"acy"_ustr, u"\u0430"_ustr }, + { u"aelig"_ustr, u"\u00E6"_ustr }, + { u"af"_ustr, u"\u2061"_ustr }, + { u"afr"_ustr, u"\U0001D51E"_ustr }, + { u"agrave"_ustr, u"\u00E0"_ustr }, + { u"alefsym"_ustr, u"\u2135"_ustr }, + { u"aleph"_ustr, u"\u2135"_ustr }, + { u"alpha"_ustr, u"\u03B1"_ustr }, + { u"amacr"_ustr, u"\u0101"_ustr }, + { u"amalg"_ustr, u"\u2A3F"_ustr }, + { u"amp"_ustr, u"\u0026"_ustr }, + { u"and"_ustr, u"\u2227"_ustr }, + { u"andand"_ustr, u"\u2A55"_ustr }, + { u"andd"_ustr, u"\u2A5C"_ustr }, + { u"andslope"_ustr, u"\u2A58"_ustr }, + { u"andv"_ustr, u"\u2A5A"_ustr }, + { u"ang"_ustr, u"\u2220"_ustr }, + { u"ange"_ustr, u"\u29A4"_ustr }, + { u"angle"_ustr, u"\u2220"_ustr }, + { u"angmsd"_ustr, u"\u2221"_ustr }, + { u"angmsdaa"_ustr, u"\u29A8"_ustr }, + { u"angmsdab"_ustr, u"\u29A9"_ustr }, + { u"angmsdac"_ustr, u"\u29AA"_ustr }, + { u"angmsdad"_ustr, u"\u29AB"_ustr }, + { u"angmsdae"_ustr, u"\u29AC"_ustr }, + { u"angmsdaf"_ustr, u"\u29AD"_ustr }, + { u"angmsdag"_ustr, u"\u29AE"_ustr }, + { u"angmsdah"_ustr, u"\u29AF"_ustr }, + { u"angrt"_ustr, u"\u221F"_ustr }, + { u"angrtvb"_ustr, u"\u22BE"_ustr }, + { u"angrtvbd"_ustr, u"\u299D"_ustr }, + { u"angsph"_ustr, u"\u2222"_ustr }, + { u"angst"_ustr, u"\u00C5"_ustr }, + { u"angzarr"_ustr, u"\u237C"_ustr }, + { u"aogon"_ustr, u"\u0105"_ustr }, + { u"aopf"_ustr, u"\U0001D552"_ustr }, + { u"ap"_ustr, u"\u2248"_ustr }, + { u"apE"_ustr, u"\u2A70"_ustr }, + { u"apacir"_ustr, u"\u2A6F"_ustr }, + { u"ape"_ustr, u"\u224A"_ustr }, + { u"apid"_ustr, u"\u224B"_ustr }, + { u"apos"_ustr, u"\u0027"_ustr }, + { u"approx"_ustr, u"\u2248"_ustr }, + { u"approxeq"_ustr, u"\u224A"_ustr }, + { u"aring"_ustr, u"\u00E5"_ustr }, + { u"ascr"_ustr, u"\U0001D4B6"_ustr }, + { u"ast"_ustr, u"\u002A"_ustr }, + { u"asymp"_ustr, u"\u2248"_ustr }, + { u"asympeq"_ustr, u"\u224D"_ustr }, + { u"atilde"_ustr, u"\u00E3"_ustr }, + { u"auml"_ustr, u"\u00E4"_ustr }, + { u"awconint"_ustr, u"\u2233"_ustr }, + { u"awint"_ustr, u"\u2A11"_ustr }, + { u"bNot"_ustr, u"\u2AED"_ustr }, + { u"backcong"_ustr, u"\u224C"_ustr }, + { u"backepsilon"_ustr, u"\u03F6"_ustr }, + { u"backprime"_ustr, u"\u2035"_ustr }, + { u"backsim"_ustr, u"\u223D"_ustr }, + { u"backsimeq"_ustr, u"\u22CD"_ustr }, + { u"barvee"_ustr, u"\u22BD"_ustr }, + { u"barwed"_ustr, u"\u2305"_ustr }, + { u"barwedge"_ustr, u"\u2305"_ustr }, + { u"bbrk"_ustr, u"\u23B5"_ustr }, + { u"bbrktbrk"_ustr, u"\u23B6"_ustr }, + { u"bcong"_ustr, u"\u224C"_ustr }, + { u"bcy"_ustr, u"\u0431"_ustr }, + { u"bdquo"_ustr, u"\u201E"_ustr }, + { u"becaus"_ustr, u"\u2235"_ustr }, + { u"because"_ustr, u"\u2235"_ustr }, + { u"bemptyv"_ustr, u"\u29B0"_ustr }, + { u"bepsi"_ustr, u"\u03F6"_ustr }, + { u"bernou"_ustr, u"\u212C"_ustr }, + { u"beta"_ustr, u"\u03B2"_ustr }, + { u"beth"_ustr, u"\u2136"_ustr }, + { u"between"_ustr, u"\u226C"_ustr }, + { u"bfr"_ustr, u"\U0001D51F"_ustr }, + { u"bigcap"_ustr, u"\u22C2"_ustr }, + { u"bigcirc"_ustr, u"\u25EF"_ustr }, + { u"bigcup"_ustr, u"\u22C3"_ustr }, + { u"bigodot"_ustr, u"\u2A00"_ustr }, + { u"bigoplus"_ustr, u"\u2A01"_ustr }, + { u"bigotimes"_ustr, u"\u2A02"_ustr }, + { u"bigsqcup"_ustr, u"\u2A06"_ustr }, + { u"bigstar"_ustr, u"\u2605"_ustr }, + { u"bigtriangledown"_ustr, u"\u25BD"_ustr }, + { u"bigtriangleup"_ustr, u"\u25B3"_ustr }, + { u"biguplus"_ustr, u"\u2A04"_ustr }, + { u"bigvee"_ustr, u"\u22C1"_ustr }, + { u"bigwedge"_ustr, u"\u22C0"_ustr }, + { u"bkarow"_ustr, u"\u290D"_ustr }, + { u"blacklozenge"_ustr, u"\u29EB"_ustr }, + { u"blacksquare"_ustr, u"\u25AA"_ustr }, + { u"blacktriangle"_ustr, u"\u25B4"_ustr }, + { u"blacktriangledown"_ustr, u"\u25BE"_ustr }, + { u"blacktriangleleft"_ustr, u"\u25C2"_ustr }, + { u"blacktriangleright"_ustr, u"\u25B8"_ustr }, + { u"blank"_ustr, u"\u2423"_ustr }, + { u"blk12"_ustr, u"\u2592"_ustr }, + { u"blk14"_ustr, u"\u2591"_ustr }, + { u"blk34"_ustr, u"\u2593"_ustr }, + { u"block"_ustr, u"\u2588"_ustr }, + { u"bne"_ustr, u"\u003D\u20E5"_ustr }, + { u"bnequiv"_ustr, u"\u2261\u20E5"_ustr }, + { u"bnot"_ustr, u"\u2310"_ustr }, + { u"bopf"_ustr, u"\U0001D553"_ustr }, + { u"bot"_ustr, u"\u22A5"_ustr }, + { u"bottom"_ustr, u"\u22A5"_ustr }, + { u"bowtie"_ustr, u"\u22C8"_ustr }, + { u"boxDL"_ustr, u"\u2557"_ustr }, + { u"boxDR"_ustr, u"\u2554"_ustr }, + { u"boxDl"_ustr, u"\u2556"_ustr }, + { u"boxDr"_ustr, u"\u2553"_ustr }, + { u"boxH"_ustr, u"\u2550"_ustr }, + { u"boxHD"_ustr, u"\u2566"_ustr }, + { u"boxHU"_ustr, u"\u2569"_ustr }, + { u"boxHd"_ustr, u"\u2564"_ustr }, + { u"boxHu"_ustr, u"\u2567"_ustr }, + { u"boxUL"_ustr, u"\u255D"_ustr }, + { u"boxUR"_ustr, u"\u255A"_ustr }, + { u"boxUl"_ustr, u"\u255C"_ustr }, + { u"boxUr"_ustr, u"\u2559"_ustr }, + { u"boxV"_ustr, u"\u2551"_ustr }, + { u"boxVH"_ustr, u"\u256C"_ustr }, + { u"boxVL"_ustr, u"\u2563"_ustr }, + { u"boxVR"_ustr, u"\u2560"_ustr }, + { u"boxVh"_ustr, u"\u256B"_ustr }, + { u"boxVl"_ustr, u"\u2562"_ustr }, + { u"boxVr"_ustr, u"\u255F"_ustr }, + { u"boxbox"_ustr, u"\u29C9"_ustr }, + { u"boxdL"_ustr, u"\u2555"_ustr }, + { u"boxdR"_ustr, u"\u2552"_ustr }, + { u"boxdl"_ustr, u"\u2510"_ustr }, + { u"boxdr"_ustr, u"\u250C"_ustr }, + { u"boxh"_ustr, u"\u2500"_ustr }, + { u"boxhD"_ustr, u"\u2565"_ustr }, + { u"boxhU"_ustr, u"\u2568"_ustr }, + { u"boxhd"_ustr, u"\u252C"_ustr }, + { u"boxhu"_ustr, u"\u2534"_ustr }, + { u"boxminus"_ustr, u"\u229F"_ustr }, + { u"boxplus"_ustr, u"\u229E"_ustr }, + { u"boxtimes"_ustr, u"\u22A0"_ustr }, + { u"boxuL"_ustr, u"\u255B"_ustr }, + { u"boxuR"_ustr, u"\u2558"_ustr }, + { u"boxul"_ustr, u"\u2518"_ustr }, + { u"boxur"_ustr, u"\u2514"_ustr }, + { u"boxv"_ustr, u"\u2502"_ustr }, + { u"boxvH"_ustr, u"\u256A"_ustr }, + { u"boxvL"_ustr, u"\u2561"_ustr }, + { u"boxvR"_ustr, u"\u255E"_ustr }, + { u"boxvh"_ustr, u"\u253C"_ustr }, + { u"boxvl"_ustr, u"\u2524"_ustr }, + { u"boxvr"_ustr, u"\u251C"_ustr }, + { u"bprime"_ustr, u"\u2035"_ustr }, + { u"breve"_ustr, u"\u02D8"_ustr }, + { u"brvbar"_ustr, u"\u00A6"_ustr }, + { u"bscr"_ustr, u"\U0001D4B7"_ustr }, + { u"bsemi"_ustr, u"\u204F"_ustr }, + { u"bsim"_ustr, u"\u223D"_ustr }, + { u"bsime"_ustr, u"\u22CD"_ustr }, + { u"bsol"_ustr, u"\u005C"_ustr }, + { u"bsolb"_ustr, u"\u29C5"_ustr }, + { u"bsolhsub"_ustr, u"\u27C8"_ustr }, + { u"bull"_ustr, u"\u2022"_ustr }, + { u"bullet"_ustr, u"\u2022"_ustr }, + { u"bump"_ustr, u"\u224E"_ustr }, + { u"bumpE"_ustr, u"\u2AAE"_ustr }, + { u"bumpe"_ustr, u"\u224F"_ustr }, + { u"bumpeq"_ustr, u"\u224F"_ustr }, + { u"cacute"_ustr, u"\u0107"_ustr }, + { u"cap"_ustr, u"\u2229"_ustr }, + { u"capand"_ustr, u"\u2A44"_ustr }, + { u"capbrcup"_ustr, u"\u2A49"_ustr }, + { u"capcap"_ustr, u"\u2A4B"_ustr }, + { u"capcup"_ustr, u"\u2A47"_ustr }, + { u"capdot"_ustr, u"\u2A40"_ustr }, + { u"caps"_ustr, u"\u2229\uFE00"_ustr }, + { u"caret"_ustr, u"\u2041"_ustr }, + { u"caron"_ustr, u"\u02C7"_ustr }, + { u"ccaps"_ustr, u"\u2A4D"_ustr }, + { u"ccaron"_ustr, u"\u010D"_ustr }, + { u"ccedil"_ustr, u"\u00E7"_ustr }, + { u"ccirc"_ustr, u"\u0109"_ustr }, + { u"ccups"_ustr, u"\u2A4C"_ustr }, + { u"ccupssm"_ustr, u"\u2A50"_ustr }, + { u"cdot"_ustr, u"\u010B"_ustr }, + { u"cedil"_ustr, u"\u00B8"_ustr }, + { u"cemptyv"_ustr, u"\u29B2"_ustr }, + { u"cent"_ustr, u"\u00A2"_ustr }, + { u"centerdot"_ustr, u"\u00B7"_ustr }, + { u"cfr"_ustr, u"\U0001D520"_ustr }, + { u"chcy"_ustr, u"\u0447"_ustr }, + { u"check"_ustr, u"\u2713"_ustr }, + { u"checkmark"_ustr, u"\u2713"_ustr }, + { u"chi"_ustr, u"\u03C7"_ustr }, + { u"cir"_ustr, u"\u25CB"_ustr }, + { u"cirE"_ustr, u"\u29C3"_ustr }, + { u"circ"_ustr, u"\u02C6"_ustr }, + { u"circeq"_ustr, u"\u2257"_ustr }, + { u"circlearrowleft"_ustr, u"\u21BA"_ustr }, + { u"circlearrowright"_ustr, u"\u21BB"_ustr }, + { u"circledR"_ustr, u"\u00AE"_ustr }, + { u"circledS"_ustr, u"\u24C8"_ustr }, + { u"circledast"_ustr, u"\u229B"_ustr }, + { u"circledcirc"_ustr, u"\u229A"_ustr }, + { u"circleddash"_ustr, u"\u229D"_ustr }, + { u"cire"_ustr, u"\u2257"_ustr }, + { u"cirfnint"_ustr, u"\u2A10"_ustr }, + { u"cirmid"_ustr, u"\u2AEF"_ustr }, + { u"cirscir"_ustr, u"\u29C2"_ustr }, + { u"clubs"_ustr, u"\u2663"_ustr }, + { u"clubsuit"_ustr, u"\u2663"_ustr }, + { u"colon"_ustr, u"\u003A"_ustr }, + { u"colone"_ustr, u"\u2254"_ustr }, + { u"coloneq"_ustr, u"\u2254"_ustr }, + { u"comma"_ustr, u"\u002C"_ustr }, + { u"commat"_ustr, u"\u0040"_ustr }, + { u"comp"_ustr, u"\u2201"_ustr }, + { u"compfn"_ustr, u"\u2218"_ustr }, + { u"complement"_ustr, u"\u2201"_ustr }, + { u"complexes"_ustr, u"\u2102"_ustr }, + { u"cong"_ustr, u"\u2245"_ustr }, + { u"congdot"_ustr, u"\u2A6D"_ustr }, + { u"conint"_ustr, u"\u222E"_ustr }, + { u"copf"_ustr, u"\U0001D554"_ustr }, + { u"coprod"_ustr, u"\u2210"_ustr }, + { u"copy"_ustr, u"\u00A9"_ustr }, + { u"copysr"_ustr, u"\u2117"_ustr }, + { u"crarr"_ustr, u"\u21B5"_ustr }, + { u"cross"_ustr, u"\u2717"_ustr }, + { u"cscr"_ustr, u"\U0001D4B8"_ustr }, + { u"csub"_ustr, u"\u2ACF"_ustr }, + { u"csube"_ustr, u"\u2AD1"_ustr }, + { u"csup"_ustr, u"\u2AD0"_ustr }, + { u"csupe"_ustr, u"\u2AD2"_ustr }, + { u"ctdot"_ustr, u"\u22EF"_ustr }, + { u"cudarrl"_ustr, u"\u2938"_ustr }, + { u"cudarrr"_ustr, u"\u2935"_ustr }, + { u"cuepr"_ustr, u"\u22DE"_ustr }, + { u"cuesc"_ustr, u"\u22DF"_ustr }, + { u"cularr"_ustr, u"\u21B6"_ustr }, + { u"cularrp"_ustr, u"\u293D"_ustr }, + { u"cup"_ustr, u"\u222A"_ustr }, + { u"cupbrcap"_ustr, u"\u2A48"_ustr }, + { u"cupcap"_ustr, u"\u2A46"_ustr }, + { u"cupcup"_ustr, u"\u2A4A"_ustr }, + { u"cupdot"_ustr, u"\u228D"_ustr }, + { u"cupor"_ustr, u"\u2A45"_ustr }, + { u"cups"_ustr, u"\u222A\uFE00"_ustr }, + { u"curarr"_ustr, u"\u21B7"_ustr }, + { u"curarrm"_ustr, u"\u293C"_ustr }, + { u"curlyeqprec"_ustr, u"\u22DE"_ustr }, + { u"curlyeqsucc"_ustr, u"\u22DF"_ustr }, + { u"curlyvee"_ustr, u"\u22CE"_ustr }, + { u"curlywedge"_ustr, u"\u22CF"_ustr }, + { u"curren"_ustr, u"\u00A4"_ustr }, + { u"curvearrowleft"_ustr, u"\u21B6"_ustr }, + { u"curvearrowright"_ustr, u"\u21B7"_ustr }, + { u"cuvee"_ustr, u"\u22CE"_ustr }, + { u"cuwed"_ustr, u"\u22CF"_ustr }, + { u"cwconint"_ustr, u"\u2232"_ustr }, + { u"cwint"_ustr, u"\u2231"_ustr }, + { u"cylcty"_ustr, u"\u232D"_ustr }, + { u"dArr"_ustr, u"\u21D3"_ustr }, + { u"dHar"_ustr, u"\u2965"_ustr }, + { u"dagger"_ustr, u"\u2020"_ustr }, + { u"daleth"_ustr, u"\u2138"_ustr }, + { u"darr"_ustr, u"\u2193"_ustr }, + { u"dash"_ustr, u"\u2010"_ustr }, + { u"dashv"_ustr, u"\u22A3"_ustr }, + { u"dbkarow"_ustr, u"\u290F"_ustr }, + { u"dblac"_ustr, u"\u02DD"_ustr }, + { u"dcaron"_ustr, u"\u010F"_ustr }, + { u"dcy"_ustr, u"\u0434"_ustr }, + { u"dd"_ustr, u"\u2146"_ustr }, + { u"ddagger"_ustr, u"\u2021"_ustr }, + { u"ddarr"_ustr, u"\u21CA"_ustr }, + { u"ddotseq"_ustr, u"\u2A77"_ustr }, + { u"deg"_ustr, u"\u00B0"_ustr }, + { u"delta"_ustr, u"\u03B4"_ustr }, + { u"demptyv"_ustr, u"\u29B1"_ustr }, + { u"dfisht"_ustr, u"\u297F"_ustr }, + { u"dfr"_ustr, u"\U0001D521"_ustr }, + { u"dharl"_ustr, u"\u21C3"_ustr }, + { u"dharr"_ustr, u"\u21C2"_ustr }, + { u"diam"_ustr, u"\u22C4"_ustr }, + { u"diamond"_ustr, u"\u22C4"_ustr }, + { u"diamondsuit"_ustr, u"\u2666"_ustr }, + { u"diams"_ustr, u"\u2666"_ustr }, + { u"die"_ustr, u"\u00A8"_ustr }, + { u"digamma"_ustr, u"\u03DD"_ustr }, + { u"disin"_ustr, u"\u22F2"_ustr }, + { u"div"_ustr, u"\u00F7"_ustr }, + { u"divide"_ustr, u"\u00F7"_ustr }, + { u"divideontimes"_ustr, u"\u22C7"_ustr }, + { u"divonx"_ustr, u"\u22C7"_ustr }, + { u"djcy"_ustr, u"\u0452"_ustr }, + { u"dlcorn"_ustr, u"\u231E"_ustr }, + { u"dlcrop"_ustr, u"\u230D"_ustr }, + { u"dollar"_ustr, u"\u0024"_ustr }, + { u"dopf"_ustr, u"\U0001D555"_ustr }, + { u"dot"_ustr, u"\u02D9"_ustr }, + { u"doteq"_ustr, u"\u2250"_ustr }, + { u"doteqdot"_ustr, u"\u2251"_ustr }, + { u"dotminus"_ustr, u"\u2238"_ustr }, + { u"dotplus"_ustr, u"\u2214"_ustr }, + { u"dotsquare"_ustr, u"\u22A1"_ustr }, + { u"doublebarwedge"_ustr, u"\u2306"_ustr }, + { u"downarrow"_ustr, u"\u2193"_ustr }, + { u"downdownarrows"_ustr, u"\u21CA"_ustr }, + { u"downharpoonleft"_ustr, u"\u21C3"_ustr }, + { u"downharpoonright"_ustr, u"\u21C2"_ustr }, + { u"drbkarow"_ustr, u"\u2910"_ustr }, + { u"drcorn"_ustr, u"\u231F"_ustr }, + { u"drcrop"_ustr, u"\u230C"_ustr }, + { u"dscr"_ustr, u"\U0001D4B9"_ustr }, + { u"dscy"_ustr, u"\u0455"_ustr }, + { u"dsol"_ustr, u"\u29F6"_ustr }, + { u"dstrok"_ustr, u"\u0111"_ustr }, + { u"dtdot"_ustr, u"\u22F1"_ustr }, + { u"dtri"_ustr, u"\u25BF"_ustr }, + { u"dtrif"_ustr, u"\u25BE"_ustr }, + { u"duarr"_ustr, u"\u21F5"_ustr }, + { u"duhar"_ustr, u"\u296F"_ustr }, + { u"dwangle"_ustr, u"\u29A6"_ustr }, + { u"dzcy"_ustr, u"\u045F"_ustr }, + { u"dzigrarr"_ustr, u"\u27FF"_ustr }, + { u"eDDot"_ustr, u"\u2A77"_ustr }, + { u"eDot"_ustr, u"\u2251"_ustr }, + { u"eacute"_ustr, u"\u00E9"_ustr }, + { u"easter"_ustr, u"\u2A6E"_ustr }, + { u"ecaron"_ustr, u"\u011B"_ustr }, + { u"ecir"_ustr, u"\u2256"_ustr }, + { u"ecirc"_ustr, u"\u00EA"_ustr }, + { u"ecolon"_ustr, u"\u2255"_ustr }, + { u"ecy"_ustr, u"\u044D"_ustr }, + { u"edot"_ustr, u"\u0117"_ustr }, + { u"ee"_ustr, u"\u2147"_ustr }, + { u"efDot"_ustr, u"\u2252"_ustr }, + { u"efr"_ustr, u"\U0001D522"_ustr }, + { u"eg"_ustr, u"\u2A9A"_ustr }, + { u"egrave"_ustr, u"\u00E8"_ustr }, + { u"egs"_ustr, u"\u2A96"_ustr }, + { u"egsdot"_ustr, u"\u2A98"_ustr }, + { u"el"_ustr, u"\u2A99"_ustr }, + { u"elinters"_ustr, u"\u23E7"_ustr }, + { u"ell"_ustr, u"\u2113"_ustr }, + { u"els"_ustr, u"\u2A95"_ustr }, + { u"elsdot"_ustr, u"\u2A97"_ustr }, + { u"emacr"_ustr, u"\u0113"_ustr }, + { u"empty"_ustr, u"\u2205"_ustr }, + { u"emptyset"_ustr, u"\u2205"_ustr }, + { u"emptyv"_ustr, u"\u2205"_ustr }, + { u"emsp"_ustr, u"\u2003"_ustr }, + { u"emsp13"_ustr, u"\u2004"_ustr }, + { u"emsp14"_ustr, u"\u2005"_ustr }, + { u"eng"_ustr, u"\u014B"_ustr }, + { u"ensp"_ustr, u"\u2002"_ustr }, + { u"eogon"_ustr, u"\u0119"_ustr }, + { u"eopf"_ustr, u"\U0001D556"_ustr }, + { u"epar"_ustr, u"\u22D5"_ustr }, + { u"eparsl"_ustr, u"\u29E3"_ustr }, + { u"eplus"_ustr, u"\u2A71"_ustr }, + { u"epsi"_ustr, u"\u03B5"_ustr }, + { u"epsilon"_ustr, u"\u03B5"_ustr }, + { u"epsiv"_ustr, u"\u03F5"_ustr }, + { u"eqcirc"_ustr, u"\u2256"_ustr }, + { u"eqcolon"_ustr, u"\u2255"_ustr }, + { u"eqsim"_ustr, u"\u2242"_ustr }, + { u"eqslantgtr"_ustr, u"\u2A96"_ustr }, + { u"eqslantless"_ustr, u"\u2A95"_ustr }, + { u"equals"_ustr, u"\u003D"_ustr }, + { u"equest"_ustr, u"\u225F"_ustr }, + { u"equiv"_ustr, u"\u2261"_ustr }, + { u"equivDD"_ustr, u"\u2A78"_ustr }, + { u"eqvparsl"_ustr, u"\u29E5"_ustr }, + { u"erDot"_ustr, u"\u2253"_ustr }, + { u"erarr"_ustr, u"\u2971"_ustr }, + { u"escr"_ustr, u"\u212F"_ustr }, + { u"esdot"_ustr, u"\u2250"_ustr }, + { u"esim"_ustr, u"\u2242"_ustr }, + { u"eta"_ustr, u"\u03B7"_ustr }, + { u"eth"_ustr, u"\u00F0"_ustr }, + { u"euml"_ustr, u"\u00EB"_ustr }, + { u"euro"_ustr, u"\u20AC"_ustr }, + { u"excl"_ustr, u"\u0021"_ustr }, + { u"exist"_ustr, u"\u2203"_ustr }, + { u"expectation"_ustr, u"\u2130"_ustr }, + { u"exponentiale"_ustr, u"\u2147"_ustr }, + { u"fallingdotseq"_ustr, u"\u2252"_ustr }, + { u"fcy"_ustr, u"\u0444"_ustr }, + { u"female"_ustr, u"\u2640"_ustr }, + { u"ffilig"_ustr, u"\uFB03"_ustr }, + { u"fflig"_ustr, u"\uFB00"_ustr }, + { u"ffllig"_ustr, u"\uFB04"_ustr }, + { u"ffr"_ustr, u"\U0001D523"_ustr }, + { u"filig"_ustr, u"\uFB01"_ustr }, + { u"fjlig"_ustr, u"\u0066\u006A"_ustr }, + { u"flat"_ustr, u"\u266D"_ustr }, + { u"fllig"_ustr, u"\uFB02"_ustr }, + { u"fltns"_ustr, u"\u25B1"_ustr }, + { u"fnof"_ustr, u"\u0192"_ustr }, + { u"fopf"_ustr, u"\U0001D557"_ustr }, + { u"forall"_ustr, u"\u2200"_ustr }, + { u"fork"_ustr, u"\u22D4"_ustr }, + { u"forkv"_ustr, u"\u2AD9"_ustr }, + { u"fpartint"_ustr, u"\u2A0D"_ustr }, + { u"frac12"_ustr, u"\u00BD"_ustr }, + { u"frac13"_ustr, u"\u2153"_ustr }, + { u"frac14"_ustr, u"\u00BC"_ustr }, + { u"frac15"_ustr, u"\u2155"_ustr }, + { u"frac16"_ustr, u"\u2159"_ustr }, + { u"frac18"_ustr, u"\u215B"_ustr }, + { u"frac23"_ustr, u"\u2154"_ustr }, + { u"frac25"_ustr, u"\u2156"_ustr }, + { u"frac34"_ustr, u"\u00BE"_ustr }, + { u"frac35"_ustr, u"\u2157"_ustr }, + { u"frac38"_ustr, u"\u215C"_ustr }, + { u"frac45"_ustr, u"\u2158"_ustr }, + { u"frac56"_ustr, u"\u215A"_ustr }, + { u"frac58"_ustr, u"\u215D"_ustr }, + { u"frac78"_ustr, u"\u215E"_ustr }, + { u"frasl"_ustr, u"\u2044"_ustr }, + { u"frown"_ustr, u"\u2322"_ustr }, + { u"fscr"_ustr, u"\U0001D4BB"_ustr }, + { u"gE"_ustr, u"\u2267"_ustr }, + { u"gEl"_ustr, u"\u2A8C"_ustr }, + { u"gacute"_ustr, u"\u01F5"_ustr }, + { u"gamma"_ustr, u"\u03B3"_ustr }, + { u"gammad"_ustr, u"\u03DD"_ustr }, + { u"gap"_ustr, u"\u2A86"_ustr }, + { u"gbreve"_ustr, u"\u011F"_ustr }, + { u"gcirc"_ustr, u"\u011D"_ustr }, + { u"gcy"_ustr, u"\u0433"_ustr }, + { u"gdot"_ustr, u"\u0121"_ustr }, + { u"ge"_ustr, u"\u2265"_ustr }, + { u"gel"_ustr, u"\u22DB"_ustr }, + { u"geq"_ustr, u"\u2265"_ustr }, + { u"geqq"_ustr, u"\u2267"_ustr }, + { u"geqslant"_ustr, u"\u2A7E"_ustr }, + { u"ges"_ustr, u"\u2A7E"_ustr }, + { u"gescc"_ustr, u"\u2AA9"_ustr }, + { u"gesdot"_ustr, u"\u2A80"_ustr }, + { u"gesdoto"_ustr, u"\u2A82"_ustr }, + { u"gesdotol"_ustr, u"\u2A84"_ustr }, + { u"gesl"_ustr, u"\u22DB\uFE00"_ustr }, + { u"gesles"_ustr, u"\u2A94"_ustr }, + { u"gfr"_ustr, u"\U0001D524"_ustr }, + { u"gg"_ustr, u"\u226B"_ustr }, + { u"ggg"_ustr, u"\u22D9"_ustr }, + { u"gimel"_ustr, u"\u2137"_ustr }, + { u"gjcy"_ustr, u"\u0453"_ustr }, + { u"gl"_ustr, u"\u2277"_ustr }, + { u"glE"_ustr, u"\u2A92"_ustr }, + { u"gla"_ustr, u"\u2AA5"_ustr }, + { u"glj"_ustr, u"\u2AA4"_ustr }, + { u"gnE"_ustr, u"\u2269"_ustr }, + { u"gnap"_ustr, u"\u2A8A"_ustr }, + { u"gnapprox"_ustr, u"\u2A8A"_ustr }, + { u"gne"_ustr, u"\u2A88"_ustr }, + { u"gneq"_ustr, u"\u2A88"_ustr }, + { u"gneqq"_ustr, u"\u2269"_ustr }, + { u"gnsim"_ustr, u"\u22E7"_ustr }, + { u"gopf"_ustr, u"\U0001D558"_ustr }, + { u"grave"_ustr, u"\u0060"_ustr }, + { u"gscr"_ustr, u"\u210A"_ustr }, + { u"gsim"_ustr, u"\u2273"_ustr }, + { u"gsime"_ustr, u"\u2A8E"_ustr }, + { u"gsiml"_ustr, u"\u2A90"_ustr }, + { u"gt"_ustr, u"\u003E"_ustr }, + { u"gtcc"_ustr, u"\u2AA7"_ustr }, + { u"gtcir"_ustr, u"\u2A7A"_ustr }, + { u"gtdot"_ustr, u"\u22D7"_ustr }, + { u"gtlPar"_ustr, u"\u2995"_ustr }, + { u"gtquest"_ustr, u"\u2A7C"_ustr }, + { u"gtrapprox"_ustr, u"\u2A86"_ustr }, + { u"gtrarr"_ustr, u"\u2978"_ustr }, + { u"gtrdot"_ustr, u"\u22D7"_ustr }, + { u"gtreqless"_ustr, u"\u22DB"_ustr }, + { u"gtreqqless"_ustr, u"\u2A8C"_ustr }, + { u"gtrless"_ustr, u"\u2277"_ustr }, + { u"gtrsim"_ustr, u"\u2273"_ustr }, + { u"gvertneqq"_ustr, u"\u2269\uFE00"_ustr }, + { u"gvnE"_ustr, u"\u2269\uFE00"_ustr }, + { u"hArr"_ustr, u"\u21D4"_ustr }, + { u"hairsp"_ustr, u"\u200A"_ustr }, + { u"half"_ustr, u"\u00BD"_ustr }, + { u"hamilt"_ustr, u"\u210B"_ustr }, + { u"hardcy"_ustr, u"\u044A"_ustr }, + { u"harr"_ustr, u"\u2194"_ustr }, + { u"harrcir"_ustr, u"\u2948"_ustr }, + { u"harrw"_ustr, u"\u21AD"_ustr }, + { u"hbar"_ustr, u"\u210F"_ustr }, + { u"hcirc"_ustr, u"\u0125"_ustr }, + { u"hearts"_ustr, u"\u2665"_ustr }, + { u"heartsuit"_ustr, u"\u2665"_ustr }, + { u"hellip"_ustr, u"\u2026"_ustr }, + { u"hercon"_ustr, u"\u22B9"_ustr }, + { u"hfr"_ustr, u"\U0001D525"_ustr }, + { u"hksearow"_ustr, u"\u2925"_ustr }, + { u"hkswarow"_ustr, u"\u2926"_ustr }, + { u"hoarr"_ustr, u"\u21FF"_ustr }, + { u"homtht"_ustr, u"\u223B"_ustr }, + { u"hookleftarrow"_ustr, u"\u21A9"_ustr }, + { u"hookrightarrow"_ustr, u"\u21AA"_ustr }, + { u"hopf"_ustr, u"\U0001D559"_ustr }, + { u"horbar"_ustr, u"\u2015"_ustr }, + { u"hscr"_ustr, u"\U0001D4BD"_ustr }, + { u"hslash"_ustr, u"\u210F"_ustr }, + { u"hstrok"_ustr, u"\u0127"_ustr }, + { u"hybull"_ustr, u"\u2043"_ustr }, + { u"hyphen"_ustr, u"\u2010"_ustr }, + { u"iacute"_ustr, u"\u00ED"_ustr }, + { u"ic"_ustr, u"\u2063"_ustr }, + { u"icirc"_ustr, u"\u00EE"_ustr }, + { u"icy"_ustr, u"\u0438"_ustr }, + { u"iecy"_ustr, u"\u0435"_ustr }, + { u"iexcl"_ustr, u"\u00A1"_ustr }, + { u"iff"_ustr, u"\u21D4"_ustr }, + { u"ifr"_ustr, u"\U0001D526"_ustr }, + { u"igrave"_ustr, u"\u00EC"_ustr }, + { u"ii"_ustr, u"\u2148"_ustr }, + { u"iiiint"_ustr, u"\u2A0C"_ustr }, + { u"iiint"_ustr, u"\u222D"_ustr }, + { u"iinfin"_ustr, u"\u29DC"_ustr }, + { u"iiota"_ustr, u"\u2129"_ustr }, + { u"ijlig"_ustr, u"\u0133"_ustr }, + { u"imacr"_ustr, u"\u012B"_ustr }, + { u"image"_ustr, u"\u2111"_ustr }, + { u"imagline"_ustr, u"\u2110"_ustr }, + { u"imagpart"_ustr, u"\u2111"_ustr }, + { u"imath"_ustr, u"\u0131"_ustr }, + { u"imof"_ustr, u"\u22B7"_ustr }, + { u"imped"_ustr, u"\u01B5"_ustr }, + { u"in"_ustr, u"\u2208"_ustr }, + { u"incare"_ustr, u"\u2105"_ustr }, + { u"infin"_ustr, u"\u221E"_ustr }, + { u"infintie"_ustr, u"\u29DD"_ustr }, + { u"inodot"_ustr, u"\u0131"_ustr }, + { u"int"_ustr, u"\u222B"_ustr }, + { u"intcal"_ustr, u"\u22BA"_ustr }, + { u"integers"_ustr, u"\u2124"_ustr }, + { u"intercal"_ustr, u"\u22BA"_ustr }, + { u"intlarhk"_ustr, u"\u2A17"_ustr }, + { u"intprod"_ustr, u"\u2A3C"_ustr }, + { u"iocy"_ustr, u"\u0451"_ustr }, + { u"iogon"_ustr, u"\u012F"_ustr }, + { u"iopf"_ustr, u"\U0001D55A"_ustr }, + { u"iota"_ustr, u"\u03B9"_ustr }, + { u"iprod"_ustr, u"\u2A3C"_ustr }, + { u"iquest"_ustr, u"\u00BF"_ustr }, + { u"iscr"_ustr, u"\U0001D4BE"_ustr }, + { u"isin"_ustr, u"\u2208"_ustr }, + { u"isinE"_ustr, u"\u22F9"_ustr }, + { u"isindot"_ustr, u"\u22F5"_ustr }, + { u"isins"_ustr, u"\u22F4"_ustr }, + { u"isinsv"_ustr, u"\u22F3"_ustr }, + { u"isinv"_ustr, u"\u2208"_ustr }, + { u"it"_ustr, u"\u2062"_ustr }, + { u"itilde"_ustr, u"\u0129"_ustr }, + { u"iukcy"_ustr, u"\u0456"_ustr }, + { u"iuml"_ustr, u"\u00EF"_ustr }, + { u"jcirc"_ustr, u"\u0135"_ustr }, + { u"jcy"_ustr, u"\u0439"_ustr }, + { u"jfr"_ustr, u"\U0001D527"_ustr }, + { u"jmath"_ustr, u"\u0237"_ustr }, + { u"jopf"_ustr, u"\U0001D55B"_ustr }, + { u"jscr"_ustr, u"\U0001D4BF"_ustr }, + { u"jsercy"_ustr, u"\u0458"_ustr }, + { u"jukcy"_ustr, u"\u0454"_ustr }, + { u"kappa"_ustr, u"\u03BA"_ustr }, + { u"kappav"_ustr, u"\u03F0"_ustr }, + { u"kcedil"_ustr, u"\u0137"_ustr }, + { u"kcy"_ustr, u"\u043A"_ustr }, + { u"kfr"_ustr, u"\U0001D528"_ustr }, + { u"kgreen"_ustr, u"\u0138"_ustr }, + { u"khcy"_ustr, u"\u0445"_ustr }, + { u"kjcy"_ustr, u"\u045C"_ustr }, + { u"kopf"_ustr, u"\U0001D55C"_ustr }, + { u"kscr"_ustr, u"\U0001D4C0"_ustr }, + { u"lAarr"_ustr, u"\u21DA"_ustr }, + { u"lArr"_ustr, u"\u21D0"_ustr }, + { u"lAtail"_ustr, u"\u291B"_ustr }, + { u"lBarr"_ustr, u"\u290E"_ustr }, + { u"lE"_ustr, u"\u2266"_ustr }, + { u"lEg"_ustr, u"\u2A8B"_ustr }, + { u"lHar"_ustr, u"\u2962"_ustr }, + { u"lacute"_ustr, u"\u013A"_ustr }, + { u"laemptyv"_ustr, u"\u29B4"_ustr }, + { u"lagran"_ustr, u"\u2112"_ustr }, + { u"lambda"_ustr, u"\u03BB"_ustr }, + { u"lang"_ustr, u"\u27E8"_ustr }, + { u"langd"_ustr, u"\u2991"_ustr }, + { u"langle"_ustr, u"\u27E8"_ustr }, + { u"lap"_ustr, u"\u2A85"_ustr }, + { u"laquo"_ustr, u"\u00AB"_ustr }, + { u"larr"_ustr, u"\u2190"_ustr }, + { u"larrb"_ustr, u"\u21E4"_ustr }, + { u"larrbfs"_ustr, u"\u291F"_ustr }, + { u"larrfs"_ustr, u"\u291D"_ustr }, + { u"larrhk"_ustr, u"\u21A9"_ustr }, + { u"larrlp"_ustr, u"\u21AB"_ustr }, + { u"larrpl"_ustr, u"\u2939"_ustr }, + { u"larrsim"_ustr, u"\u2973"_ustr }, + { u"larrtl"_ustr, u"\u21A2"_ustr }, + { u"lat"_ustr, u"\u2AAB"_ustr }, + { u"latail"_ustr, u"\u2919"_ustr }, + { u"late"_ustr, u"\u2AAD"_ustr }, + { u"lates"_ustr, u"\u2AAD\uFE00"_ustr }, + { u"lbarr"_ustr, u"\u290C"_ustr }, + { u"lbbrk"_ustr, u"\u2772"_ustr }, + { u"lbrace"_ustr, u"\u007B"_ustr }, + { u"lbrack"_ustr, u"\u005B"_ustr }, + { u"lbrke"_ustr, u"\u298B"_ustr }, + { u"lbrksld"_ustr, u"\u298F"_ustr }, + { u"lbrkslu"_ustr, u"\u298D"_ustr }, + { u"lcaron"_ustr, u"\u013E"_ustr }, + { u"lcedil"_ustr, u"\u013C"_ustr }, + { u"lceil"_ustr, u"\u2308"_ustr }, + { u"lcub"_ustr, u"\u007B"_ustr }, + { u"lcy"_ustr, u"\u043B"_ustr }, + { u"ldca"_ustr, u"\u2936"_ustr }, + { u"ldquo"_ustr, u"\u201C"_ustr }, + { u"ldquor"_ustr, u"\u201E"_ustr }, + { u"ldrdhar"_ustr, u"\u2967"_ustr }, + { u"ldrushar"_ustr, u"\u294B"_ustr }, + { u"ldsh"_ustr, u"\u21B2"_ustr }, + { u"le"_ustr, u"\u2264"_ustr }, + { u"leftarrow"_ustr, u"\u2190"_ustr }, + { u"leftarrowtail"_ustr, u"\u21A2"_ustr }, + { u"leftharpoondown"_ustr, u"\u21BD"_ustr }, + { u"leftharpoonup"_ustr, u"\u21BC"_ustr }, + { u"leftleftarrows"_ustr, u"\u21C7"_ustr }, + { u"leftrightarrow"_ustr, u"\u2194"_ustr }, + { u"leftrightarrows"_ustr, u"\u21C6"_ustr }, + { u"leftrightharpoons"_ustr, u"\u21CB"_ustr }, + { u"leftrightsquigarrow"_ustr, u"\u21AD"_ustr }, + { u"leftthreetimes"_ustr, u"\u22CB"_ustr }, + { u"leg"_ustr, u"\u22DA"_ustr }, + { u"leq"_ustr, u"\u2264"_ustr }, + { u"leqq"_ustr, u"\u2266"_ustr }, + { u"leqslant"_ustr, u"\u2A7D"_ustr }, + { u"les"_ustr, u"\u2A7D"_ustr }, + { u"lescc"_ustr, u"\u2AA8"_ustr }, + { u"lesdot"_ustr, u"\u2A7F"_ustr }, + { u"lesdoto"_ustr, u"\u2A81"_ustr }, + { u"lesdotor"_ustr, u"\u2A83"_ustr }, + { u"lesg"_ustr, u"\u22DA\uFE00"_ustr }, + { u"lesges"_ustr, u"\u2A93"_ustr }, + { u"lessapprox"_ustr, u"\u2A85"_ustr }, + { u"lessdot"_ustr, u"\u22D6"_ustr }, + { u"lesseqgtr"_ustr, u"\u22DA"_ustr }, + { u"lesseqqgtr"_ustr, u"\u2A8B"_ustr }, + { u"lessgtr"_ustr, u"\u2276"_ustr }, + { u"lesssim"_ustr, u"\u2272"_ustr }, + { u"lfisht"_ustr, u"\u297C"_ustr }, + { u"lfloor"_ustr, u"\u230A"_ustr }, + { u"lfr"_ustr, u"\U0001D529"_ustr }, + { u"lg"_ustr, u"\u2276"_ustr }, + { u"lgE"_ustr, u"\u2A91"_ustr }, + { u"lhard"_ustr, u"\u21BD"_ustr }, + { u"lharu"_ustr, u"\u21BC"_ustr }, + { u"lharul"_ustr, u"\u296A"_ustr }, + { u"lhblk"_ustr, u"\u2584"_ustr }, + { u"ljcy"_ustr, u"\u0459"_ustr }, + { u"ll"_ustr, u"\u226A"_ustr }, + { u"llarr"_ustr, u"\u21C7"_ustr }, + { u"llcorner"_ustr, u"\u231E"_ustr }, + { u"llhard"_ustr, u"\u296B"_ustr }, + { u"lltri"_ustr, u"\u25FA"_ustr }, + { u"lmidot"_ustr, u"\u0140"_ustr }, + { u"lmoust"_ustr, u"\u23B0"_ustr }, + { u"lmoustache"_ustr, u"\u23B0"_ustr }, + { u"lnE"_ustr, u"\u2268"_ustr }, + { u"lnap"_ustr, u"\u2A89"_ustr }, + { u"lnapprox"_ustr, u"\u2A89"_ustr }, + { u"lne"_ustr, u"\u2A87"_ustr }, + { u"lneq"_ustr, u"\u2A87"_ustr }, + { u"lneqq"_ustr, u"\u2268"_ustr }, + { u"lnsim"_ustr, u"\u22E6"_ustr }, + { u"loang"_ustr, u"\u27EC"_ustr }, + { u"loarr"_ustr, u"\u21FD"_ustr }, + { u"lobrk"_ustr, u"\u27E6"_ustr }, + { u"longleftarrow"_ustr, u"\u27F5"_ustr }, + { u"longleftrightarrow"_ustr, u"\u27F7"_ustr }, + { u"longmapsto"_ustr, u"\u27FC"_ustr }, + { u"longrightarrow"_ustr, u"\u27F6"_ustr }, + { u"looparrowleft"_ustr, u"\u21AB"_ustr }, + { u"looparrowright"_ustr, u"\u21AC"_ustr }, + { u"lopar"_ustr, u"\u2985"_ustr }, + { u"lopf"_ustr, u"\U0001D55D"_ustr }, + { u"loplus"_ustr, u"\u2A2D"_ustr }, + { u"lotimes"_ustr, u"\u2A34"_ustr }, + { u"lowast"_ustr, u"\u2217"_ustr }, + { u"lowbar"_ustr, u"\u005F"_ustr }, + { u"loz"_ustr, u"\u25CA"_ustr }, + { u"lozenge"_ustr, u"\u25CA"_ustr }, + { u"lozf"_ustr, u"\u29EB"_ustr }, + { u"lpar"_ustr, u"\u0028"_ustr }, + { u"lparlt"_ustr, u"\u2993"_ustr }, + { u"lrarr"_ustr, u"\u21C6"_ustr }, + { u"lrcorner"_ustr, u"\u231F"_ustr }, + { u"lrhar"_ustr, u"\u21CB"_ustr }, + { u"lrhard"_ustr, u"\u296D"_ustr }, + { u"lrm"_ustr, u"\u200E"_ustr }, + { u"lrtri"_ustr, u"\u22BF"_ustr }, + { u"lsaquo"_ustr, u"\u2039"_ustr }, + { u"lscr"_ustr, u"\U0001D4C1"_ustr }, + { u"lsh"_ustr, u"\u21B0"_ustr }, + { u"lsim"_ustr, u"\u2272"_ustr }, + { u"lsime"_ustr, u"\u2A8D"_ustr }, + { u"lsimg"_ustr, u"\u2A8F"_ustr }, + { u"lsqb"_ustr, u"\u005B"_ustr }, + { u"lsquo"_ustr, u"\u2018"_ustr }, + { u"lsquor"_ustr, u"\u201A"_ustr }, + { u"lstrok"_ustr, u"\u0142"_ustr }, + { u"lt"_ustr, u"\u003C"_ustr }, + { u"ltcc"_ustr, u"\u2AA6"_ustr }, + { u"ltcir"_ustr, u"\u2A79"_ustr }, + { u"ltdot"_ustr, u"\u22D6"_ustr }, + { u"lthree"_ustr, u"\u22CB"_ustr }, + { u"ltimes"_ustr, u"\u22C9"_ustr }, + { u"ltlarr"_ustr, u"\u2976"_ustr }, + { u"ltquest"_ustr, u"\u2A7B"_ustr }, + { u"ltrPar"_ustr, u"\u2996"_ustr }, + { u"ltri"_ustr, u"\u25C3"_ustr }, + { u"ltrie"_ustr, u"\u22B4"_ustr }, + { u"ltrif"_ustr, u"\u25C2"_ustr }, + { u"lurdshar"_ustr, u"\u294A"_ustr }, + { u"luruhar"_ustr, u"\u2966"_ustr }, + { u"lvertneqq"_ustr, u"\u2268\uFE00"_ustr }, + { u"lvnE"_ustr, u"\u2268\uFE00"_ustr }, + { u"mDDot"_ustr, u"\u223A"_ustr }, + { u"macr"_ustr, u"\u00AF"_ustr }, + { u"male"_ustr, u"\u2642"_ustr }, + { u"malt"_ustr, u"\u2720"_ustr }, + { u"maltese"_ustr, u"\u2720"_ustr }, + { u"map"_ustr, u"\u21A6"_ustr }, + { u"mapsto"_ustr, u"\u21A6"_ustr }, + { u"mapstodown"_ustr, u"\u21A7"_ustr }, + { u"mapstoleft"_ustr, u"\u21A4"_ustr }, + { u"mapstoup"_ustr, u"\u21A5"_ustr }, + { u"marker"_ustr, u"\u25AE"_ustr }, + { u"mcomma"_ustr, u"\u2A29"_ustr }, + { u"mcy"_ustr, u"\u043C"_ustr }, + { u"mdash"_ustr, u"\u2014"_ustr }, + { u"measuredangle"_ustr, u"\u2221"_ustr }, + { u"mfr"_ustr, u"\U0001D52A"_ustr }, + { u"mho"_ustr, u"\u2127"_ustr }, + { u"micro"_ustr, u"\u00B5"_ustr }, + { u"mid"_ustr, u"\u2223"_ustr }, + { u"midast"_ustr, u"\u002A"_ustr }, + { u"midcir"_ustr, u"\u2AF0"_ustr }, + { u"middot"_ustr, u"\u00B7"_ustr }, + { u"minus"_ustr, u"\u2212"_ustr }, + { u"minusb"_ustr, u"\u229F"_ustr }, + { u"minusd"_ustr, u"\u2238"_ustr }, + { u"minusdu"_ustr, u"\u2A2A"_ustr }, + { u"mlcp"_ustr, u"\u2ADB"_ustr }, + { u"mldr"_ustr, u"\u2026"_ustr }, + { u"mnplus"_ustr, u"\u2213"_ustr }, + { u"models"_ustr, u"\u22A7"_ustr }, + { u"mopf"_ustr, u"\U0001D55E"_ustr }, + { u"mp"_ustr, u"\u2213"_ustr }, + { u"mscr"_ustr, u"\U0001D4C2"_ustr }, + { u"mstpos"_ustr, u"\u223E"_ustr }, + { u"mu"_ustr, u"\u03BC"_ustr }, + { u"multimap"_ustr, u"\u22B8"_ustr }, + { u"mumap"_ustr, u"\u22B8"_ustr }, + { u"nGg"_ustr, u"\u22D9\u0338"_ustr }, + { u"nGt"_ustr, u"\u226B\u20D2"_ustr }, + { u"nGtv"_ustr, u"\u226B\u0338"_ustr }, + { u"nLeftarrow"_ustr, u"\u21CD"_ustr }, + { u"nLeftrightarrow"_ustr, u"\u21CE"_ustr }, + { u"nLl"_ustr, u"\u22D8\u0338"_ustr }, + { u"nLt"_ustr, u"\u226A\u20D2"_ustr }, + { u"nLtv"_ustr, u"\u226A\u0338"_ustr }, + { u"nRightarrow"_ustr, u"\u21CF"_ustr }, + { u"nVDash"_ustr, u"\u22AF"_ustr }, + { u"nVdash"_ustr, u"\u22AE"_ustr }, + { u"nabla"_ustr, u"\u2207"_ustr }, + { u"nacute"_ustr, u"\u0144"_ustr }, + { u"nang"_ustr, u"\u2220\u20D2"_ustr }, + { u"nap"_ustr, u"\u2249"_ustr }, + { u"napE"_ustr, u"\u2A70\u0338"_ustr }, + { u"napid"_ustr, u"\u224B\u0338"_ustr }, + { u"napos"_ustr, u"\u0149"_ustr }, + { u"napprox"_ustr, u"\u2249"_ustr }, + { u"natur"_ustr, u"\u266E"_ustr }, + { u"natural"_ustr, u"\u266E"_ustr }, + { u"naturals"_ustr, u"\u2115"_ustr }, + { u"nbsp"_ustr, u"\u00A0"_ustr }, + { u"nbump"_ustr, u"\u224E\u0338"_ustr }, + { u"nbumpe"_ustr, u"\u224F\u0338"_ustr }, + { u"ncap"_ustr, u"\u2A43"_ustr }, + { u"ncaron"_ustr, u"\u0148"_ustr }, + { u"ncedil"_ustr, u"\u0146"_ustr }, + { u"ncong"_ustr, u"\u2247"_ustr }, + { u"ncongdot"_ustr, u"\u2A6D\u0338"_ustr }, + { u"ncup"_ustr, u"\u2A42"_ustr }, + { u"ncy"_ustr, u"\u043D"_ustr }, + { u"ndash"_ustr, u"\u2013"_ustr }, + { u"ne"_ustr, u"\u2260"_ustr }, + { u"neArr"_ustr, u"\u21D7"_ustr }, + { u"nearhk"_ustr, u"\u2924"_ustr }, + { u"nearr"_ustr, u"\u2197"_ustr }, + { u"nearrow"_ustr, u"\u2197"_ustr }, + { u"nedot"_ustr, u"\u2250\u0338"_ustr }, + { u"nequiv"_ustr, u"\u2262"_ustr }, + { u"nesear"_ustr, u"\u2928"_ustr }, + { u"nesim"_ustr, u"\u2242\u0338"_ustr }, + { u"nexist"_ustr, u"\u2204"_ustr }, + { u"nexists"_ustr, u"\u2204"_ustr }, + { u"nfr"_ustr, u"\U0001D52B"_ustr }, + { u"ngE"_ustr, u"\u2267\u0338"_ustr }, + { u"nge"_ustr, u"\u2271"_ustr }, + { u"ngeq"_ustr, u"\u2271"_ustr }, + { u"ngeqq"_ustr, u"\u2267\u0338"_ustr }, + { u"ngeqslant"_ustr, u"\u2A7E\u0338"_ustr }, + { u"nges"_ustr, u"\u2A7E\u0338"_ustr }, + { u"ngsim"_ustr, u"\u2275"_ustr }, + { u"ngt"_ustr, u"\u226F"_ustr }, + { u"ngtr"_ustr, u"\u226F"_ustr }, + { u"nhArr"_ustr, u"\u21CE"_ustr }, + { u"nharr"_ustr, u"\u21AE"_ustr }, + { u"nhpar"_ustr, u"\u2AF2"_ustr }, + { u"ni"_ustr, u"\u220B"_ustr }, + { u"nis"_ustr, u"\u22FC"_ustr }, + { u"nisd"_ustr, u"\u22FA"_ustr }, + { u"niv"_ustr, u"\u220B"_ustr }, + { u"njcy"_ustr, u"\u045A"_ustr }, + { u"nlArr"_ustr, u"\u21CD"_ustr }, + { u"nlE"_ustr, u"\u2266\u0338"_ustr }, + { u"nlarr"_ustr, u"\u219A"_ustr }, + { u"nldr"_ustr, u"\u2025"_ustr }, + { u"nle"_ustr, u"\u2270"_ustr }, + { u"nleftarrow"_ustr, u"\u219A"_ustr }, + { u"nleftrightarrow"_ustr, u"\u21AE"_ustr }, + { u"nleq"_ustr, u"\u2270"_ustr }, + { u"nleqq"_ustr, u"\u2266\u0338"_ustr }, + { u"nleqslant"_ustr, u"\u2A7D\u0338"_ustr }, + { u"nles"_ustr, u"\u2A7D\u0338"_ustr }, + { u"nless"_ustr, u"\u226E"_ustr }, + { u"nlsim"_ustr, u"\u2274"_ustr }, + { u"nlt"_ustr, u"\u226E"_ustr }, + { u"nltri"_ustr, u"\u22EA"_ustr }, + { u"nltrie"_ustr, u"\u22EC"_ustr }, + { u"nmid"_ustr, u"\u2224"_ustr }, + { u"nopf"_ustr, u"\U0001D55F"_ustr }, + { u"not"_ustr, u"\u00AC"_ustr }, + { u"notin"_ustr, u"\u2209"_ustr }, + { u"notinE"_ustr, u"\u22F9\u0338"_ustr }, + { u"notindot"_ustr, u"\u22F5\u0338"_ustr }, + { u"notinva"_ustr, u"\u2209"_ustr }, + { u"notinvb"_ustr, u"\u22F7"_ustr }, + { u"notinvc"_ustr, u"\u22F6"_ustr }, + { u"notni"_ustr, u"\u220C"_ustr }, + { u"notniva"_ustr, u"\u220C"_ustr }, + { u"notnivb"_ustr, u"\u22FE"_ustr }, + { u"notnivc"_ustr, u"\u22FD"_ustr }, + { u"npar"_ustr, u"\u2226"_ustr }, + { u"nparallel"_ustr, u"\u2226"_ustr }, + { u"nparsl"_ustr, u"\u2AFD\u20E5"_ustr }, + { u"npart"_ustr, u"\u2202\u0338"_ustr }, + { u"npolint"_ustr, u"\u2A14"_ustr }, + { u"npr"_ustr, u"\u2280"_ustr }, + { u"nprcue"_ustr, u"\u22E0"_ustr }, + { u"npre"_ustr, u"\u2AAF\u0338"_ustr }, + { u"nprec"_ustr, u"\u2280"_ustr }, + { u"npreceq"_ustr, u"\u2AAF\u0338"_ustr }, + { u"nrArr"_ustr, u"\u21CF"_ustr }, + { u"nrarr"_ustr, u"\u219B"_ustr }, + { u"nrarrc"_ustr, u"\u2933\u0338"_ustr }, + { u"nrarrw"_ustr, u"\u219D\u0338"_ustr }, + { u"nrightarrow"_ustr, u"\u219B"_ustr }, + { u"nrtri"_ustr, u"\u22EB"_ustr }, + { u"nrtrie"_ustr, u"\u22ED"_ustr }, + { u"nsc"_ustr, u"\u2281"_ustr }, + { u"nsccue"_ustr, u"\u22E1"_ustr }, + { u"nsce"_ustr, u"\u2AB0\u0338"_ustr }, + { u"nscr"_ustr, u"\U0001D4C3"_ustr }, + { u"nshortmid"_ustr, u"\u2224"_ustr }, + { u"nshortparallel"_ustr, u"\u2226"_ustr }, + { u"nsim"_ustr, u"\u2241"_ustr }, + { u"nsime"_ustr, u"\u2244"_ustr }, + { u"nsimeq"_ustr, u"\u2244"_ustr }, + { u"nsmid"_ustr, u"\u2224"_ustr }, + { u"nspar"_ustr, u"\u2226"_ustr }, + { u"nsqsube"_ustr, u"\u22E2"_ustr }, + { u"nsqsupe"_ustr, u"\u22E3"_ustr }, + { u"nsub"_ustr, u"\u2284"_ustr }, + { u"nsubE"_ustr, u"\u2AC5\u0338"_ustr }, + { u"nsube"_ustr, u"\u2288"_ustr }, + { u"nsubset"_ustr, u"\u2282\u20D2"_ustr }, + { u"nsubseteq"_ustr, u"\u2288"_ustr }, + { u"nsubseteqq"_ustr, u"\u2AC5\u0338"_ustr }, + { u"nsucc"_ustr, u"\u2281"_ustr }, + { u"nsucceq"_ustr, u"\u2AB0\u0338"_ustr }, + { u"nsup"_ustr, u"\u2285"_ustr }, + { u"nsupE"_ustr, u"\u2AC6\u0338"_ustr }, + { u"nsupe"_ustr, u"\u2289"_ustr }, + { u"nsupset"_ustr, u"\u2283\u20D2"_ustr }, + { u"nsupseteq"_ustr, u"\u2289"_ustr }, + { u"nsupseteqq"_ustr, u"\u2AC6\u0338"_ustr }, + { u"ntgl"_ustr, u"\u2279"_ustr }, + { u"ntilde"_ustr, u"\u00F1"_ustr }, + { u"ntlg"_ustr, u"\u2278"_ustr }, + { u"ntriangleleft"_ustr, u"\u22EA"_ustr }, + { u"ntrianglelefteq"_ustr, u"\u22EC"_ustr }, + { u"ntriangleright"_ustr, u"\u22EB"_ustr }, + { u"ntrianglerighteq"_ustr, u"\u22ED"_ustr }, + { u"nu"_ustr, u"\u03BD"_ustr }, + { u"num"_ustr, u"\u0023"_ustr }, + { u"numero"_ustr, u"\u2116"_ustr }, + { u"numsp"_ustr, u"\u2007"_ustr }, + { u"nvDash"_ustr, u"\u22AD"_ustr }, + { u"nvHarr"_ustr, u"\u2904"_ustr }, + { u"nvap"_ustr, u"\u224D\u20D2"_ustr }, + { u"nvdash"_ustr, u"\u22AC"_ustr }, + { u"nvge"_ustr, u"\u2265\u20D2"_ustr }, + { u"nvgt"_ustr, u"\u003E\u20D2"_ustr }, + { u"nvinfin"_ustr, u"\u29DE"_ustr }, + { u"nvlArr"_ustr, u"\u2902"_ustr }, + { u"nvle"_ustr, u"\u2264\u20D2"_ustr }, + { u"nvlt"_ustr, u"\u003C\u20D2"_ustr }, + { u"nvltrie"_ustr, u"\u22B4\u20D2"_ustr }, + { u"nvrArr"_ustr, u"\u2903"_ustr }, + { u"nvrtrie"_ustr, u"\u22B5\u20D2"_ustr }, + { u"nvsim"_ustr, u"\u223C\u20D2"_ustr }, + { u"nwArr"_ustr, u"\u21D6"_ustr }, + { u"nwarhk"_ustr, u"\u2923"_ustr }, + { u"nwarr"_ustr, u"\u2196"_ustr }, + { u"nwarrow"_ustr, u"\u2196"_ustr }, + { u"nwnear"_ustr, u"\u2927"_ustr }, + { u"oS"_ustr, u"\u24C8"_ustr }, + { u"oacute"_ustr, u"\u00F3"_ustr }, + { u"oast"_ustr, u"\u229B"_ustr }, + { u"ocir"_ustr, u"\u229A"_ustr }, + { u"ocirc"_ustr, u"\u00F4"_ustr }, + { u"ocy"_ustr, u"\u043E"_ustr }, + { u"odash"_ustr, u"\u229D"_ustr }, + { u"odblac"_ustr, u"\u0151"_ustr }, + { u"odiv"_ustr, u"\u2A38"_ustr }, + { u"odot"_ustr, u"\u2299"_ustr }, + { u"odsold"_ustr, u"\u29BC"_ustr }, + { u"oelig"_ustr, u"\u0153"_ustr }, + { u"ofcir"_ustr, u"\u29BF"_ustr }, + { u"ofr"_ustr, u"\U0001D52C"_ustr }, + { u"ogon"_ustr, u"\u02DB"_ustr }, + { u"ograve"_ustr, u"\u00F2"_ustr }, + { u"ogt"_ustr, u"\u29C1"_ustr }, + { u"ohbar"_ustr, u"\u29B5"_ustr }, + { u"ohm"_ustr, u"\u03A9"_ustr }, + { u"oint"_ustr, u"\u222E"_ustr }, + { u"olarr"_ustr, u"\u21BA"_ustr }, + { u"olcir"_ustr, u"\u29BE"_ustr }, + { u"olcross"_ustr, u"\u29BB"_ustr }, + { u"oline"_ustr, u"\u203E"_ustr }, + { u"olt"_ustr, u"\u29C0"_ustr }, + { u"omacr"_ustr, u"\u014D"_ustr }, + { u"omega"_ustr, u"\u03C9"_ustr }, + { u"omicron"_ustr, u"\u03BF"_ustr }, + { u"omid"_ustr, u"\u29B6"_ustr }, + { u"ominus"_ustr, u"\u2296"_ustr }, + { u"oopf"_ustr, u"\U0001D560"_ustr }, + { u"opar"_ustr, u"\u29B7"_ustr }, + { u"operp"_ustr, u"\u29B9"_ustr }, + { u"oplus"_ustr, u"\u2295"_ustr }, + { u"or"_ustr, u"\u2228"_ustr }, + { u"orarr"_ustr, u"\u21BB"_ustr }, + { u"ord"_ustr, u"\u2A5D"_ustr }, + { u"order"_ustr, u"\u2134"_ustr }, + { u"orderof"_ustr, u"\u2134"_ustr }, + { u"ordf"_ustr, u"\u00AA"_ustr }, + { u"ordm"_ustr, u"\u00BA"_ustr }, + { u"origof"_ustr, u"\u22B6"_ustr }, + { u"oror"_ustr, u"\u2A56"_ustr }, + { u"orslope"_ustr, u"\u2A57"_ustr }, + { u"orv"_ustr, u"\u2A5B"_ustr }, + { u"oscr"_ustr, u"\u2134"_ustr }, + { u"oslash"_ustr, u"\u00F8"_ustr }, + { u"osol"_ustr, u"\u2298"_ustr }, + { u"otilde"_ustr, u"\u00F5"_ustr }, + { u"otimes"_ustr, u"\u2297"_ustr }, + { u"otimesas"_ustr, u"\u2A36"_ustr }, + { u"ouml"_ustr, u"\u00F6"_ustr }, + { u"ovbar"_ustr, u"\u233D"_ustr }, + { u"par"_ustr, u"\u2225"_ustr }, + { u"para"_ustr, u"\u00B6"_ustr }, + { u"parallel"_ustr, u"\u2225"_ustr }, + { u"parsim"_ustr, u"\u2AF3"_ustr }, + { u"parsl"_ustr, u"\u2AFD"_ustr }, + { u"part"_ustr, u"\u2202"_ustr }, + { u"pcy"_ustr, u"\u043F"_ustr }, + { u"percnt"_ustr, u"\u0025"_ustr }, + { u"period"_ustr, u"\u002E"_ustr }, + { u"permil"_ustr, u"\u2030"_ustr }, + { u"perp"_ustr, u"\u22A5"_ustr }, + { u"pertenk"_ustr, u"\u2031"_ustr }, + { u"pfr"_ustr, u"\U0001D52D"_ustr }, + { u"phi"_ustr, u"\u03C6"_ustr }, + { u"phiv"_ustr, u"\u03D5"_ustr }, + { u"phmmat"_ustr, u"\u2133"_ustr }, + { u"phone"_ustr, u"\u260E"_ustr }, + { u"pi"_ustr, u"\u03C0"_ustr }, + { u"pitchfork"_ustr, u"\u22D4"_ustr }, + { u"piv"_ustr, u"\u03D6"_ustr }, + { u"planck"_ustr, u"\u210F"_ustr }, + { u"planckh"_ustr, u"\u210E"_ustr }, + { u"plankv"_ustr, u"\u210F"_ustr }, + { u"plus"_ustr, u"\u002B"_ustr }, + { u"plusacir"_ustr, u"\u2A23"_ustr }, + { u"plusb"_ustr, u"\u229E"_ustr }, + { u"pluscir"_ustr, u"\u2A22"_ustr }, + { u"plusdo"_ustr, u"\u2214"_ustr }, + { u"plusdu"_ustr, u"\u2A25"_ustr }, + { u"pluse"_ustr, u"\u2A72"_ustr }, + { u"plusmn"_ustr, u"\u00B1"_ustr }, + { u"plussim"_ustr, u"\u2A26"_ustr }, + { u"plustwo"_ustr, u"\u2A27"_ustr }, + { u"pm"_ustr, u"\u00B1"_ustr }, + { u"pointint"_ustr, u"\u2A15"_ustr }, + { u"popf"_ustr, u"\U0001D561"_ustr }, + { u"pound"_ustr, u"\u00A3"_ustr }, + { u"pr"_ustr, u"\u227A"_ustr }, + { u"prE"_ustr, u"\u2AB3"_ustr }, + { u"prap"_ustr, u"\u2AB7"_ustr }, + { u"prcue"_ustr, u"\u227C"_ustr }, + { u"pre"_ustr, u"\u2AAF"_ustr }, + { u"prec"_ustr, u"\u227A"_ustr }, + { u"precapprox"_ustr, u"\u2AB7"_ustr }, + { u"preccurlyeq"_ustr, u"\u227C"_ustr }, + { u"preceq"_ustr, u"\u2AAF"_ustr }, + { u"precnapprox"_ustr, u"\u2AB9"_ustr }, + { u"precneqq"_ustr, u"\u2AB5"_ustr }, + { u"precnsim"_ustr, u"\u22E8"_ustr }, + { u"precsim"_ustr, u"\u227E"_ustr }, + { u"prime"_ustr, u"\u2032"_ustr }, + { u"primes"_ustr, u"\u2119"_ustr }, + { u"prnE"_ustr, u"\u2AB5"_ustr }, + { u"prnap"_ustr, u"\u2AB9"_ustr }, + { u"prnsim"_ustr, u"\u22E8"_ustr }, + { u"prod"_ustr, u"\u220F"_ustr }, + { u"profalar"_ustr, u"\u232E"_ustr }, + { u"profline"_ustr, u"\u2312"_ustr }, + { u"profsurf"_ustr, u"\u2313"_ustr }, + { u"prop"_ustr, u"\u221D"_ustr }, + { u"propto"_ustr, u"\u221D"_ustr }, + { u"prsim"_ustr, u"\u227E"_ustr }, + { u"prurel"_ustr, u"\u22B0"_ustr }, + { u"pscr"_ustr, u"\U0001D4C5"_ustr }, + { u"psi"_ustr, u"\u03C8"_ustr }, + { u"puncsp"_ustr, u"\u2008"_ustr }, + { u"qfr"_ustr, u"\U0001D52E"_ustr }, + { u"qint"_ustr, u"\u2A0C"_ustr }, + { u"qopf"_ustr, u"\U0001D562"_ustr }, + { u"qprime"_ustr, u"\u2057"_ustr }, + { u"qscr"_ustr, u"\U0001D4C6"_ustr }, + { u"quaternions"_ustr, u"\u210D"_ustr }, + { u"quatint"_ustr, u"\u2A16"_ustr }, + { u"quest"_ustr, u"\u003F"_ustr }, + { u"questeq"_ustr, u"\u225F"_ustr }, + { u"quot"_ustr, u"\u0022"_ustr }, + { u"rAarr"_ustr, u"\u21DB"_ustr }, + { u"rArr"_ustr, u"\u21D2"_ustr }, + { u"rAtail"_ustr, u"\u291C"_ustr }, + { u"rBarr"_ustr, u"\u290F"_ustr }, + { u"rHar"_ustr, u"\u2964"_ustr }, + { u"race"_ustr, u"\u223D\u0331"_ustr }, + { u"racute"_ustr, u"\u0155"_ustr }, + { u"radic"_ustr, u"\u221A"_ustr }, + { u"raemptyv"_ustr, u"\u29B3"_ustr }, + { u"rang"_ustr, u"\u27E9"_ustr }, + { u"rangd"_ustr, u"\u2992"_ustr }, + { u"range"_ustr, u"\u29A5"_ustr }, + { u"rangle"_ustr, u"\u27E9"_ustr }, + { u"raquo"_ustr, u"\u00BB"_ustr }, + { u"rarr"_ustr, u"\u2192"_ustr }, + { u"rarrap"_ustr, u"\u2975"_ustr }, + { u"rarrb"_ustr, u"\u21E5"_ustr }, + { u"rarrbfs"_ustr, u"\u2920"_ustr }, + { u"rarrc"_ustr, u"\u2933"_ustr }, + { u"rarrfs"_ustr, u"\u291E"_ustr }, + { u"rarrhk"_ustr, u"\u21AA"_ustr }, + { u"rarrlp"_ustr, u"\u21AC"_ustr }, + { u"rarrpl"_ustr, u"\u2945"_ustr }, + { u"rarrsim"_ustr, u"\u2974"_ustr }, + { u"rarrtl"_ustr, u"\u21A3"_ustr }, + { u"rarrw"_ustr, u"\u219D"_ustr }, + { u"ratail"_ustr, u"\u291A"_ustr }, + { u"ratio"_ustr, u"\u2236"_ustr }, + { u"rationals"_ustr, u"\u211A"_ustr }, + { u"rbarr"_ustr, u"\u290D"_ustr }, + { u"rbbrk"_ustr, u"\u2773"_ustr }, + { u"rbrace"_ustr, u"\u007D"_ustr }, + { u"rbrack"_ustr, u"\u005D"_ustr }, + { u"rbrke"_ustr, u"\u298C"_ustr }, + { u"rbrksld"_ustr, u"\u298E"_ustr }, + { u"rbrkslu"_ustr, u"\u2990"_ustr }, + { u"rcaron"_ustr, u"\u0159"_ustr }, + { u"rcedil"_ustr, u"\u0157"_ustr }, + { u"rceil"_ustr, u"\u2309"_ustr }, + { u"rcub"_ustr, u"\u007D"_ustr }, + { u"rcy"_ustr, u"\u0440"_ustr }, + { u"rdca"_ustr, u"\u2937"_ustr }, + { u"rdldhar"_ustr, u"\u2969"_ustr }, + { u"rdquo"_ustr, u"\u201D"_ustr }, + { u"rdquor"_ustr, u"\u201D"_ustr }, + { u"rdsh"_ustr, u"\u21B3"_ustr }, + { u"real"_ustr, u"\u211C"_ustr }, + { u"realine"_ustr, u"\u211B"_ustr }, + { u"realpart"_ustr, u"\u211C"_ustr }, + { u"reals"_ustr, u"\u211D"_ustr }, + { u"rect"_ustr, u"\u25AD"_ustr }, + { u"reg"_ustr, u"\u00AE"_ustr }, + { u"rfisht"_ustr, u"\u297D"_ustr }, + { u"rfloor"_ustr, u"\u230B"_ustr }, + { u"rfr"_ustr, u"\U0001D52F"_ustr }, + { u"rhard"_ustr, u"\u21C1"_ustr }, + { u"rharu"_ustr, u"\u21C0"_ustr }, + { u"rharul"_ustr, u"\u296C"_ustr }, + { u"rho"_ustr, u"\u03C1"_ustr }, + { u"rhov"_ustr, u"\u03F1"_ustr }, + { u"rightarrow"_ustr, u"\u2192"_ustr }, + { u"rightarrowtail"_ustr, u"\u21A3"_ustr }, + { u"rightharpoondown"_ustr, u"\u21C1"_ustr }, + { u"rightharpoonup"_ustr, u"\u21C0"_ustr }, + { u"rightleftarrows"_ustr, u"\u21C4"_ustr }, + { u"rightleftharpoons"_ustr, u"\u21CC"_ustr }, + { u"rightrightarrows"_ustr, u"\u21C9"_ustr }, + { u"rightsquigarrow"_ustr, u"\u219D"_ustr }, + { u"rightthreetimes"_ustr, u"\u22CC"_ustr }, + { u"ring"_ustr, u"\u02DA"_ustr }, + { u"risingdotseq"_ustr, u"\u2253"_ustr }, + { u"rlarr"_ustr, u"\u21C4"_ustr }, + { u"rlhar"_ustr, u"\u21CC"_ustr }, + { u"rlm"_ustr, u"\u200F"_ustr }, + { u"rmoust"_ustr, u"\u23B1"_ustr }, + { u"rmoustache"_ustr, u"\u23B1"_ustr }, + { u"rnmid"_ustr, u"\u2AEE"_ustr }, + { u"roang"_ustr, u"\u27ED"_ustr }, + { u"roarr"_ustr, u"\u21FE"_ustr }, + { u"robrk"_ustr, u"\u27E7"_ustr }, + { u"ropar"_ustr, u"\u2986"_ustr }, + { u"ropf"_ustr, u"\U0001D563"_ustr }, + { u"roplus"_ustr, u"\u2A2E"_ustr }, + { u"rotimes"_ustr, u"\u2A35"_ustr }, + { u"rpar"_ustr, u"\u0029"_ustr }, + { u"rpargt"_ustr, u"\u2994"_ustr }, + { u"rppolint"_ustr, u"\u2A12"_ustr }, + { u"rrarr"_ustr, u"\u21C9"_ustr }, + { u"rsaquo"_ustr, u"\u203A"_ustr }, + { u"rscr"_ustr, u"\U0001D4C7"_ustr }, + { u"rsh"_ustr, u"\u21B1"_ustr }, + { u"rsqb"_ustr, u"\u005D"_ustr }, + { u"rsquo"_ustr, u"\u2019"_ustr }, + { u"rsquor"_ustr, u"\u2019"_ustr }, + { u"rthree"_ustr, u"\u22CC"_ustr }, + { u"rtimes"_ustr, u"\u22CA"_ustr }, + { u"rtri"_ustr, u"\u25B9"_ustr }, + { u"rtrie"_ustr, u"\u22B5"_ustr }, + { u"rtrif"_ustr, u"\u25B8"_ustr }, + { u"rtriltri"_ustr, u"\u29CE"_ustr }, + { u"ruluhar"_ustr, u"\u2968"_ustr }, + { u"rx"_ustr, u"\u211E"_ustr }, + { u"sacute"_ustr, u"\u015B"_ustr }, + { u"sbquo"_ustr, u"\u201A"_ustr }, + { u"sc"_ustr, u"\u227B"_ustr }, + { u"scE"_ustr, u"\u2AB4"_ustr }, + { u"scap"_ustr, u"\u2AB8"_ustr }, + { u"scaron"_ustr, u"\u0161"_ustr }, + { u"sccue"_ustr, u"\u227D"_ustr }, + { u"sce"_ustr, u"\u2AB0"_ustr }, + { u"scedil"_ustr, u"\u015F"_ustr }, + { u"scirc"_ustr, u"\u015D"_ustr }, + { u"scnE"_ustr, u"\u2AB6"_ustr }, + { u"scnap"_ustr, u"\u2ABA"_ustr }, + { u"scnsim"_ustr, u"\u22E9"_ustr }, + { u"scpolint"_ustr, u"\u2A13"_ustr }, + { u"scsim"_ustr, u"\u227F"_ustr }, + { u"scy"_ustr, u"\u0441"_ustr }, + { u"sdot"_ustr, u"\u22C5"_ustr }, + { u"sdotb"_ustr, u"\u22A1"_ustr }, + { u"sdote"_ustr, u"\u2A66"_ustr }, + { u"seArr"_ustr, u"\u21D8"_ustr }, + { u"searhk"_ustr, u"\u2925"_ustr }, + { u"searr"_ustr, u"\u2198"_ustr }, + { u"searrow"_ustr, u"\u2198"_ustr }, + { u"sect"_ustr, u"\u00A7"_ustr }, + { u"semi"_ustr, u"\u003B"_ustr }, + { u"seswar"_ustr, u"\u2929"_ustr }, + { u"setminus"_ustr, u"\u2216"_ustr }, + { u"setmn"_ustr, u"\u2216"_ustr }, + { u"sext"_ustr, u"\u2736"_ustr }, + { u"sfr"_ustr, u"\U0001D530"_ustr }, + { u"sfrown"_ustr, u"\u2322"_ustr }, + { u"sharp"_ustr, u"\u266F"_ustr }, + { u"shchcy"_ustr, u"\u0449"_ustr }, + { u"shcy"_ustr, u"\u0448"_ustr }, + { u"shortmid"_ustr, u"\u2223"_ustr }, + { u"shortparallel"_ustr, u"\u2225"_ustr }, + { u"shy"_ustr, u"\u00AD"_ustr }, + { u"sigma"_ustr, u"\u03C3"_ustr }, + { u"sigmaf"_ustr, u"\u03C2"_ustr }, + { u"sigmav"_ustr, u"\u03C2"_ustr }, + { u"sim"_ustr, u"\u223C"_ustr }, + { u"simdot"_ustr, u"\u2A6A"_ustr }, + { u"sime"_ustr, u"\u2243"_ustr }, + { u"simeq"_ustr, u"\u2243"_ustr }, + { u"simg"_ustr, u"\u2A9E"_ustr }, + { u"simgE"_ustr, u"\u2AA0"_ustr }, + { u"siml"_ustr, u"\u2A9D"_ustr }, + { u"simlE"_ustr, u"\u2A9F"_ustr }, + { u"simne"_ustr, u"\u2246"_ustr }, + { u"simplus"_ustr, u"\u2A24"_ustr }, + { u"simrarr"_ustr, u"\u2972"_ustr }, + { u"slarr"_ustr, u"\u2190"_ustr }, + { u"smallsetminus"_ustr, u"\u2216"_ustr }, + { u"smashp"_ustr, u"\u2A33"_ustr }, + { u"smeparsl"_ustr, u"\u29E4"_ustr }, + { u"smid"_ustr, u"\u2223"_ustr }, + { u"smile"_ustr, u"\u2323"_ustr }, + { u"smt"_ustr, u"\u2AAA"_ustr }, + { u"smte"_ustr, u"\u2AAC"_ustr }, + { u"smtes"_ustr, u"\u2AAC\uFE00"_ustr }, + { u"softcy"_ustr, u"\u044C"_ustr }, + { u"sol"_ustr, u"\u002F"_ustr }, + { u"solb"_ustr, u"\u29C4"_ustr }, + { u"solbar"_ustr, u"\u233F"_ustr }, + { u"sopf"_ustr, u"\U0001D564"_ustr }, + { u"spades"_ustr, u"\u2660"_ustr }, + { u"spadesuit"_ustr, u"\u2660"_ustr }, + { u"spar"_ustr, u"\u2225"_ustr }, + { u"sqcap"_ustr, u"\u2293"_ustr }, + { u"sqcaps"_ustr, u"\u2293\uFE00"_ustr }, + { u"sqcup"_ustr, u"\u2294"_ustr }, + { u"sqcups"_ustr, u"\u2294\uFE00"_ustr }, + { u"sqsub"_ustr, u"\u228F"_ustr }, + { u"sqsube"_ustr, u"\u2291"_ustr }, + { u"sqsubset"_ustr, u"\u228F"_ustr }, + { u"sqsubseteq"_ustr, u"\u2291"_ustr }, + { u"sqsup"_ustr, u"\u2290"_ustr }, + { u"sqsupe"_ustr, u"\u2292"_ustr }, + { u"sqsupset"_ustr, u"\u2290"_ustr }, + { u"sqsupseteq"_ustr, u"\u2292"_ustr }, + { u"squ"_ustr, u"\u25A1"_ustr }, + { u"square"_ustr, u"\u25A1"_ustr }, + { u"squarf"_ustr, u"\u25AA"_ustr }, + { u"squf"_ustr, u"\u25AA"_ustr }, + { u"srarr"_ustr, u"\u2192"_ustr }, + { u"sscr"_ustr, u"\U0001D4C8"_ustr }, + { u"ssetmn"_ustr, u"\u2216"_ustr }, + { u"ssmile"_ustr, u"\u2323"_ustr }, + { u"sstarf"_ustr, u"\u22C6"_ustr }, + { u"star"_ustr, u"\u2606"_ustr }, + { u"starf"_ustr, u"\u2605"_ustr }, + { u"straightepsilon"_ustr, u"\u03F5"_ustr }, + { u"straightphi"_ustr, u"\u03D5"_ustr }, + { u"strns"_ustr, u"\u00AF"_ustr }, + { u"sub"_ustr, u"\u2282"_ustr }, + { u"subE"_ustr, u"\u2AC5"_ustr }, + { u"subdot"_ustr, u"\u2ABD"_ustr }, + { u"sube"_ustr, u"\u2286"_ustr }, + { u"subedot"_ustr, u"\u2AC3"_ustr }, + { u"submult"_ustr, u"\u2AC1"_ustr }, + { u"subnE"_ustr, u"\u2ACB"_ustr }, + { u"subne"_ustr, u"\u228A"_ustr }, + { u"subplus"_ustr, u"\u2ABF"_ustr }, + { u"subrarr"_ustr, u"\u2979"_ustr }, + { u"subset"_ustr, u"\u2282"_ustr }, + { u"subseteq"_ustr, u"\u2286"_ustr }, + { u"subseteqq"_ustr, u"\u2AC5"_ustr }, + { u"subsetneq"_ustr, u"\u228A"_ustr }, + { u"subsetneqq"_ustr, u"\u2ACB"_ustr }, + { u"subsim"_ustr, u"\u2AC7"_ustr }, + { u"subsub"_ustr, u"\u2AD5"_ustr }, + { u"subsup"_ustr, u"\u2AD3"_ustr }, + { u"succ"_ustr, u"\u227B"_ustr }, + { u"succapprox"_ustr, u"\u2AB8"_ustr }, + { u"succcurlyeq"_ustr, u"\u227D"_ustr }, + { u"succeq"_ustr, u"\u2AB0"_ustr }, + { u"succnapprox"_ustr, u"\u2ABA"_ustr }, + { u"succneqq"_ustr, u"\u2AB6"_ustr }, + { u"succnsim"_ustr, u"\u22E9"_ustr }, + { u"succsim"_ustr, u"\u227F"_ustr }, + { u"sum"_ustr, u"\u2211"_ustr }, + { u"sung"_ustr, u"\u266A"_ustr }, + { u"sup"_ustr, u"\u2283"_ustr }, + { u"sup1"_ustr, u"\u00B9"_ustr }, + { u"sup2"_ustr, u"\u00B2"_ustr }, + { u"sup3"_ustr, u"\u00B3"_ustr }, + { u"supE"_ustr, u"\u2AC6"_ustr }, + { u"supdot"_ustr, u"\u2ABE"_ustr }, + { u"supdsub"_ustr, u"\u2AD8"_ustr }, + { u"supe"_ustr, u"\u2287"_ustr }, + { u"supedot"_ustr, u"\u2AC4"_ustr }, + { u"suphsol"_ustr, u"\u27C9"_ustr }, + { u"suphsub"_ustr, u"\u2AD7"_ustr }, + { u"suplarr"_ustr, u"\u297B"_ustr }, + { u"supmult"_ustr, u"\u2AC2"_ustr }, + { u"supnE"_ustr, u"\u2ACC"_ustr }, + { u"supne"_ustr, u"\u228B"_ustr }, + { u"supplus"_ustr, u"\u2AC0"_ustr }, + { u"supset"_ustr, u"\u2283"_ustr }, + { u"supseteq"_ustr, u"\u2287"_ustr }, + { u"supseteqq"_ustr, u"\u2AC6"_ustr }, + { u"supsetneq"_ustr, u"\u228B"_ustr }, + { u"supsetneqq"_ustr, u"\u2ACC"_ustr }, + { u"supsim"_ustr, u"\u2AC8"_ustr }, + { u"supsub"_ustr, u"\u2AD4"_ustr }, + { u"supsup"_ustr, u"\u2AD6"_ustr }, + { u"swArr"_ustr, u"\u21D9"_ustr }, + { u"swarhk"_ustr, u"\u2926"_ustr }, + { u"swarr"_ustr, u"\u2199"_ustr }, + { u"swarrow"_ustr, u"\u2199"_ustr }, + { u"swnwar"_ustr, u"\u292A"_ustr }, + { u"szlig"_ustr, u"\u00DF"_ustr }, + { u"target"_ustr, u"\u2316"_ustr }, + { u"tau"_ustr, u"\u03C4"_ustr }, + { u"tbrk"_ustr, u"\u23B4"_ustr }, + { u"tcaron"_ustr, u"\u0165"_ustr }, + { u"tcedil"_ustr, u"\u0163"_ustr }, + { u"tcy"_ustr, u"\u0442"_ustr }, + { u"tdot"_ustr, u"\u20DB"_ustr }, + { u"telrec"_ustr, u"\u2315"_ustr }, + { u"tfr"_ustr, u"\U0001D531"_ustr }, + { u"there4"_ustr, u"\u2234"_ustr }, + { u"therefore"_ustr, u"\u2234"_ustr }, + { u"theta"_ustr, u"\u03B8"_ustr }, + { u"thetasym"_ustr, u"\u03D1"_ustr }, + { u"thetav"_ustr, u"\u03D1"_ustr }, + { u"thickapprox"_ustr, u"\u2248"_ustr }, + { u"thicksim"_ustr, u"\u223C"_ustr }, + { u"thinsp"_ustr, u"\u2009"_ustr }, + { u"thkap"_ustr, u"\u2248"_ustr }, + { u"thksim"_ustr, u"\u223C"_ustr }, + { u"thorn"_ustr, u"\u00FE"_ustr }, + { u"tilde"_ustr, u"\u02DC"_ustr }, + { u"times"_ustr, u"\u00D7"_ustr }, + { u"timesb"_ustr, u"\u22A0"_ustr }, + { u"timesbar"_ustr, u"\u2A31"_ustr }, + { u"timesd"_ustr, u"\u2A30"_ustr }, + { u"tint"_ustr, u"\u222D"_ustr }, + { u"toea"_ustr, u"\u2928"_ustr }, + { u"top"_ustr, u"\u22A4"_ustr }, + { u"topbot"_ustr, u"\u2336"_ustr }, + { u"topcir"_ustr, u"\u2AF1"_ustr }, + { u"topf"_ustr, u"\U0001D565"_ustr }, + { u"topfork"_ustr, u"\u2ADA"_ustr }, + { u"tosa"_ustr, u"\u2929"_ustr }, + { u"tprime"_ustr, u"\u2034"_ustr }, + { u"trade"_ustr, u"\u2122"_ustr }, + { u"triangle"_ustr, u"\u25B5"_ustr }, + { u"triangledown"_ustr, u"\u25BF"_ustr }, + { u"triangleleft"_ustr, u"\u25C3"_ustr }, + { u"trianglelefteq"_ustr, u"\u22B4"_ustr }, + { u"triangleq"_ustr, u"\u225C"_ustr }, + { u"triangleright"_ustr, u"\u25B9"_ustr }, + { u"trianglerighteq"_ustr, u"\u22B5"_ustr }, + { u"tridot"_ustr, u"\u25EC"_ustr }, + { u"trie"_ustr, u"\u225C"_ustr }, + { u"triminus"_ustr, u"\u2A3A"_ustr }, + { u"triplus"_ustr, u"\u2A39"_ustr }, + { u"trisb"_ustr, u"\u29CD"_ustr }, + { u"tritime"_ustr, u"\u2A3B"_ustr }, + { u"trpezium"_ustr, u"\u23E2"_ustr }, + { u"tscr"_ustr, u"\U0001D4C9"_ustr }, + { u"tscy"_ustr, u"\u0446"_ustr }, + { u"tshcy"_ustr, u"\u045B"_ustr }, + { u"tstrok"_ustr, u"\u0167"_ustr }, + { u"twixt"_ustr, u"\u226C"_ustr }, + { u"twoheadleftarrow"_ustr, u"\u219E"_ustr }, + { u"twoheadrightarrow"_ustr, u"\u21A0"_ustr }, + { u"uArr"_ustr, u"\u21D1"_ustr }, + { u"uHar"_ustr, u"\u2963"_ustr }, + { u"uacute"_ustr, u"\u00FA"_ustr }, + { u"uarr"_ustr, u"\u2191"_ustr }, + { u"ubrcy"_ustr, u"\u045E"_ustr }, + { u"ubreve"_ustr, u"\u016D"_ustr }, + { u"ucirc"_ustr, u"\u00FB"_ustr }, + { u"ucy"_ustr, u"\u0443"_ustr }, + { u"udarr"_ustr, u"\u21C5"_ustr }, + { u"udblac"_ustr, u"\u0171"_ustr }, + { u"udhar"_ustr, u"\u296E"_ustr }, + { u"ufisht"_ustr, u"\u297E"_ustr }, + { u"ufr"_ustr, u"\U0001D532"_ustr }, + { u"ugrave"_ustr, u"\u00F9"_ustr }, + { u"uharl"_ustr, u"\u21BF"_ustr }, + { u"uharr"_ustr, u"\u21BE"_ustr }, + { u"uhblk"_ustr, u"\u2580"_ustr }, + { u"ulcorn"_ustr, u"\u231C"_ustr }, + { u"ulcorner"_ustr, u"\u231C"_ustr }, + { u"ulcrop"_ustr, u"\u230F"_ustr }, + { u"ultri"_ustr, u"\u25F8"_ustr }, + { u"umacr"_ustr, u"\u016B"_ustr }, + { u"uml"_ustr, u"\u00A8"_ustr }, + { u"uogon"_ustr, u"\u0173"_ustr }, + { u"uopf"_ustr, u"\U0001D566"_ustr }, + { u"uparrow"_ustr, u"\u2191"_ustr }, + { u"updownarrow"_ustr, u"\u2195"_ustr }, + { u"upharpoonleft"_ustr, u"\u21BF"_ustr }, + { u"upharpoonright"_ustr, u"\u21BE"_ustr }, + { u"uplus"_ustr, u"\u228E"_ustr }, + { u"upsi"_ustr, u"\u03C5"_ustr }, + { u"upsih"_ustr, u"\u03D2"_ustr }, + { u"upsilon"_ustr, u"\u03C5"_ustr }, + { u"upuparrows"_ustr, u"\u21C8"_ustr }, + { u"urcorn"_ustr, u"\u231D"_ustr }, + { u"urcorner"_ustr, u"\u231D"_ustr }, + { u"urcrop"_ustr, u"\u230E"_ustr }, + { u"uring"_ustr, u"\u016F"_ustr }, + { u"urtri"_ustr, u"\u25F9"_ustr }, + { u"uscr"_ustr, u"\U0001D4CA"_ustr }, + { u"utdot"_ustr, u"\u22F0"_ustr }, + { u"utilde"_ustr, u"\u0169"_ustr }, + { u"utri"_ustr, u"\u25B5"_ustr }, + { u"utrif"_ustr, u"\u25B4"_ustr }, + { u"uuarr"_ustr, u"\u21C8"_ustr }, + { u"uuml"_ustr, u"\u00FC"_ustr }, + { u"uwangle"_ustr, u"\u29A7"_ustr }, + { u"vArr"_ustr, u"\u21D5"_ustr }, + { u"vBar"_ustr, u"\u2AE8"_ustr }, + { u"vBarv"_ustr, u"\u2AE9"_ustr }, + { u"vDash"_ustr, u"\u22A8"_ustr }, + { u"vangrt"_ustr, u"\u299C"_ustr }, + { u"varepsilon"_ustr, u"\u03F5"_ustr }, + { u"varkappa"_ustr, u"\u03F0"_ustr }, + { u"varnothing"_ustr, u"\u2205"_ustr }, + { u"varphi"_ustr, u"\u03D5"_ustr }, + { u"varpi"_ustr, u"\u03D6"_ustr }, + { u"varpropto"_ustr, u"\u221D"_ustr }, + { u"varr"_ustr, u"\u2195"_ustr }, + { u"varrho"_ustr, u"\u03F1"_ustr }, + { u"varsigma"_ustr, u"\u03C2"_ustr }, + { u"varsubsetneq"_ustr, u"\u228A\uFE00"_ustr }, + { u"varsubsetneqq"_ustr, u"\u2ACB\uFE00"_ustr }, + { u"varsupsetneq"_ustr, u"\u228B\uFE00"_ustr }, + { u"varsupsetneqq"_ustr, u"\u2ACC\uFE00"_ustr }, + { u"vartheta"_ustr, u"\u03D1"_ustr }, + { u"vartriangleleft"_ustr, u"\u22B2"_ustr }, + { u"vartriangleright"_ustr, u"\u22B3"_ustr }, + { u"vcy"_ustr, u"\u0432"_ustr }, + { u"vdash"_ustr, u"\u22A2"_ustr }, + { u"vee"_ustr, u"\u2228"_ustr }, + { u"veebar"_ustr, u"\u22BB"_ustr }, + { u"veeeq"_ustr, u"\u225A"_ustr }, + { u"vellip"_ustr, u"\u22EE"_ustr }, + { u"verbar"_ustr, u"\u007C"_ustr }, + { u"vert"_ustr, u"\u007C"_ustr }, + { u"vfr"_ustr, u"\U0001D533"_ustr }, + { u"vltri"_ustr, u"\u22B2"_ustr }, + { u"vnsub"_ustr, u"\u2282\u20D2"_ustr }, + { u"vnsup"_ustr, u"\u2283\u20D2"_ustr }, + { u"vopf"_ustr, u"\U0001D567"_ustr }, + { u"vprop"_ustr, u"\u221D"_ustr }, + { u"vrtri"_ustr, u"\u22B3"_ustr }, + { u"vscr"_ustr, u"\U0001D4CB"_ustr }, + { u"vsubnE"_ustr, u"\u2ACB\uFE00"_ustr }, + { u"vsubne"_ustr, u"\u228A\uFE00"_ustr }, + { u"vsupnE"_ustr, u"\u2ACC\uFE00"_ustr }, + { u"vsupne"_ustr, u"\u228B\uFE00"_ustr }, + { u"vzigzag"_ustr, u"\u299A"_ustr }, + { u"wcirc"_ustr, u"\u0175"_ustr }, + { u"wedbar"_ustr, u"\u2A5F"_ustr }, + { u"wedge"_ustr, u"\u2227"_ustr }, + { u"wedgeq"_ustr, u"\u2259"_ustr }, + { u"weierp"_ustr, u"\u2118"_ustr }, + { u"wfr"_ustr, u"\U0001D534"_ustr }, + { u"wopf"_ustr, u"\U0001D568"_ustr }, + { u"wp"_ustr, u"\u2118"_ustr }, + { u"wr"_ustr, u"\u2240"_ustr }, + { u"wreath"_ustr, u"\u2240"_ustr }, + { u"wscr"_ustr, u"\U0001D4CC"_ustr }, + { u"xcap"_ustr, u"\u22C2"_ustr }, + { u"xcirc"_ustr, u"\u25EF"_ustr }, + { u"xcup"_ustr, u"\u22C3"_ustr }, + { u"xdtri"_ustr, u"\u25BD"_ustr }, + { u"xfr"_ustr, u"\U0001D535"_ustr }, + { u"xhArr"_ustr, u"\u27FA"_ustr }, + { u"xharr"_ustr, u"\u27F7"_ustr }, + { u"xi"_ustr, u"\u03BE"_ustr }, + { u"xlArr"_ustr, u"\u27F8"_ustr }, + { u"xlarr"_ustr, u"\u27F5"_ustr }, + { u"xmap"_ustr, u"\u27FC"_ustr }, + { u"xnis"_ustr, u"\u22FB"_ustr }, + { u"xodot"_ustr, u"\u2A00"_ustr }, + { u"xopf"_ustr, u"\U0001D569"_ustr }, + { u"xoplus"_ustr, u"\u2A01"_ustr }, + { u"xotime"_ustr, u"\u2A02"_ustr }, + { u"xrArr"_ustr, u"\u27F9"_ustr }, + { u"xrarr"_ustr, u"\u27F6"_ustr }, + { u"xscr"_ustr, u"\U0001D4CD"_ustr }, + { u"xsqcup"_ustr, u"\u2A06"_ustr }, + { u"xuplus"_ustr, u"\u2A04"_ustr }, + { u"xutri"_ustr, u"\u25B3"_ustr }, + { u"xvee"_ustr, u"\u22C1"_ustr }, + { u"xwedge"_ustr, u"\u22C0"_ustr }, + { u"yacute"_ustr, u"\u00FD"_ustr }, + { u"yacy"_ustr, u"\u044F"_ustr }, + { u"ycirc"_ustr, u"\u0177"_ustr }, + { u"ycy"_ustr, u"\u044B"_ustr }, + { u"yen"_ustr, u"\u00A5"_ustr }, + { u"yfr"_ustr, u"\U0001D536"_ustr }, + { u"yicy"_ustr, u"\u0457"_ustr }, + { u"yopf"_ustr, u"\U0001D56A"_ustr }, + { u"yscr"_ustr, u"\U0001D4CE"_ustr }, + { u"yucy"_ustr, u"\u044E"_ustr }, + { u"yuml"_ustr, u"\u00FF"_ustr }, + { u"zacute"_ustr, u"\u017A"_ustr }, + { u"zcaron"_ustr, u"\u017E"_ustr }, + { u"zcy"_ustr, u"\u0437"_ustr }, + { u"zdot"_ustr, u"\u017C"_ustr }, + { u"zeetrf"_ustr, u"\u2128"_ustr }, + { u"zeta"_ustr, u"\u03B6"_ustr }, + { u"zfr"_ustr, u"\U0001D537"_ustr }, + { u"zhcy"_ustr, u"\u0436"_ustr }, + { u"zigrarr"_ustr, u"\u21DD"_ustr }, + { u"zopf"_ustr, u"\U0001D56B"_ustr }, + { u"zscr"_ustr, u"\U0001D4CF"_ustr }, + { u"zwj"_ustr, u"\u200D"_ustr }, + { u"zwnj"_ustr, u"\u200C"_ustr } + // clang-format on + }; + +const ::css::uno::Sequence<::css::beans::Pair<OUString, OUString>> + starmathdatabase::icustomMathmlHtmlEntities( + icustomMathmlHtmlEntitiesData, starmathdatabase::STARMATH_MATHMLHTML_ENTITY_NUMBER); + +static ::css::beans::Pair<::rtl::OUString, ::rtl::OUString> + icustomMathmlHtmlEntitiesNamesExportData[2] = { + // clang-format off + { u"σ"_ustr, u"\u03C3"_ustr}, + { u"∞"_ustr, u"\u221E"_ustr} + // clang-format on + }; +const ::css::uno::Sequence<::css::beans::Pair<::rtl::OUString, ::rtl::OUString>> + starmathdatabase::icustomMathmlHtmlEntitiesExport(icustomMathmlHtmlEntitiesNamesExportData, 2); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/mathtype.cxx b/starmath/source/mathtype.cxx new file mode 100644 index 0000000000..aca49f034a --- /dev/null +++ b/starmath/source/mathtype.cxx @@ -0,0 +1,3347 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "mathtype.hxx" +#include "eqnolefilehdr.hxx" + +#include <filter/msfilter/classids.hxx> +#include <osl/diagnose.h> +#include <sfx2/docfile.hxx> +#include <sot/storage.hxx> +#include <array> + +//These are the default MathType sizes +constexpr std::array<sal_Int16, 7> aSizeTable { + 12, + 8, + 6, + 24, + 10, + 12, + 12, +}; + +void MathType::Init() +{ + /* + These are the default MathType italic/bold settings If mathtype is changed + from its defaults, there is nothing we can do, as this information is not + stored in the document + */ + MathTypeFont aFont; + aUserStyles.reserve(11); + for(sal_uInt8 i=1;i<=11;i++) + { + aFont.nTface = i+128; + switch (i) + { + default: + aFont.nStyle=0; + break; + case 3: + case 4: + aFont.nStyle=1; + break; + case 7: + aFont.nStyle=2; + break; + } + aUserStyles.insert(aFont); + } +} + + +/*ToDo replace with table rather than switch, returns + sal_True in the case that the char is just a char, and + sal_False if the character is an operator which must not be + placed inside the quote sequence designed to protect + against being parsed as a keyword + + General solution required to force starmath to handle + unicode math chars the way it handles its own math + chars rather than handle them as text as it will do + for the default case below, i.e. incorrect spacing + between math symbols and ordinary text e.g. 1=2 rather + than 1 = 2 + */ +bool MathType::LookupChar(sal_Unicode nChar,OUStringBuffer &rRet,sal_uInt8 nVersion, + sal_uInt8 nTypeFace) +{ + bool bRet=false; + const char *pC = nullptr; + switch(nChar) + { + case 0x0000: + pC = " none "; + break; + case 0x00ac: + pC = " neg "; + break; + case 0x00b1: + pC = " +- "; + break; + case '(': + pC = " \\( "; + break; + case ')': + pC = " \\) "; + break; + case '[': + pC = " \\[ "; + break; + case ']': + pC = " \\] "; + break; + case '.': + pC = " \".\" "; + break; + case 0xae: + if ((nVersion < 3) && (nTypeFace == 0x86)) + pC = " rightarrow "; + else + { + rRet.append(OUStringChar(nChar)); + bRet=true; + } + break; + case 0x00fb: + if ((nVersion < 3) && (nTypeFace == 0x81)) + nChar = 0xDF; + rRet.append(OUStringChar(nChar)); + bRet=true; + break; + case 'a': + if ((nVersion < 3) && (nTypeFace == 0x84)) + nChar = 0x3b1; + rRet.append(OUStringChar(nChar)); + bRet=true; + break; + case 'b': + if ((nVersion < 3) && (nTypeFace == 0x84)) + nChar = 0x3b2; + rRet.append(OUStringChar(nChar)); + bRet=true; + break; + case 'l': + if ((nVersion < 3) && (nTypeFace == 0x84)) + nChar = 0x3bb; + rRet.append(OUStringChar(nChar)); + bRet=true; + break; + case 'n': + if ((nVersion < 3) && (nTypeFace == 0x84)) + nChar = 0x3bd; + rRet.append(OUStringChar(nChar)); + bRet=true; + break; + case 'r': + if ((nVersion < 3) && (nTypeFace == 0x84)) + nChar = 0x3c1; + rRet.append(OUStringChar(nChar)); + bRet=true; + break; + case 'D': + if ((nVersion < 3) && (nTypeFace == 0x84)) + nChar = 0x394; + rRet.append(OUStringChar(nChar)); + bRet=true; + break; + case 0xa9: + if ((nVersion < 3) && (nTypeFace == 0x82)) + nChar = '\''; + rRet.append(OUStringChar(nChar)); + bRet=true; + break; + case 0x00f1: + if ((nVersion < 3) && (nTypeFace == 0x86)) + pC = " \\rangle "; + else + { + rRet.append(OUStringChar(nChar)); + bRet=true; + } + break; + case 0x00a3: + if ((nVersion < 3) && (nTypeFace == 0x86)) + pC = " <= "; + else + { + rRet.append(OUStringChar(nChar)); + bRet=true; + } + break; + case 0x00de: + if ((nVersion < 3) && (nTypeFace == 0x86)) + pC = " drarrow "; + else + { + rRet.append(OUStringChar(nChar)); + bRet=true; + } + break; + case 0x0057: + if ((nVersion < 3) && (nTypeFace == 0x85)) + pC = " %OMEGA "; + else + { + rRet.append(OUStringChar(nChar)); + bRet=true; + } + break; + case 0x007b: + pC = " lbrace "; + break; + case 0x007c: + pC = " \\lline "; + break; + case 0x007d: + pC = " rbrace "; + break; + case 0x007e: + pC = " \"~\" "; + break; + case 0x2224: + pC = " ndivides "; + break; + case 0x2225: + pC = " parallel "; + break; + case 0x00d7: + if (nVersion < 3) + pC = " cdot "; + else + pC = " times "; + break; + case 0x00f7: + pC = " div "; + break; + case 0x019b: + pC = " lambdabar "; + break; + case 0x2026: + pC = " dotslow "; + break; + case 0x2022: + pC = " cdot "; + break; + case 0x2102: + pC = " setC "; + break; + case 0x210f: + pC = " hbar "; + break; + case 0x2111: + pC = " Im "; + break; + case 0x2115: + pC = " setN "; + break; + case 0x2118: + pC = " wp "; + break; + case 0x211a: + pC = " setQ "; + break; + case 0x211c: + pC = " Re "; + break; + case 0x211d: + pC = " setR "; + break; + case 0x2124: + pC = " setZ "; + break; + case 0x2135: + pC = " aleph "; + break; + case 0x2190: + pC = " leftarrow "; + break; + case 0x2191: + pC = " uparrow "; + break; + case 0x2192: + pC = " rightarrow "; + break; + case 0x0362: + pC = " widevec "; + break; + case 0x2193: + pC = " downarrow "; + break; + case 0x21d0: + pC = " dlarrow "; + break; + case 0x21d2: + pC = " drarrow "; + break; + case 0x21d4: + pC = " dlrarrow "; + break; + case 0x2200: + pC = " forall "; + break; + case 0x2202: + pC = " partial "; + break; + case 0x2203: + pC = " exists "; + break; + case 0x2204: + pC = " notexists "; + break; + case 0x2205: + pC = " emptyset "; + break; + case 0x2207: + pC = " nabla "; + break; + case 0x2112: + pC = " laplace "; + break; + case 0x03F6: + pC = " backepsilon "; + break; + case 0x2208: // in + case 0x2209: // notin + rRet.append(" func " + OUStringChar(nChar) + " "); + break; + case 0x220d: // owns + rRet.append(u" func \u220b "); + break; + case 0x220f: + pC = " prod "; + break; + case 0x2210: + pC = " coprod "; + break; + case 0x2211: + pC = " sum "; + break; + case 0x2212: + pC = " - "; + break; + case 0x2213: + pC = " -+ "; + break; + case 0x2217: + pC = " * "; + break; + case 0x2218: + pC = " circ "; + break; + case 0x221d: + pC = " prop "; + break; + case 0x221e: + pC = " infinity "; + break; + case 0x2227: + pC = " and "; + break; + case 0x2228: + pC = " or "; + break; + case 0x2229: + pC = " intersection "; + break; + case 0x222a: + pC = " union "; + break; + case 0x222b: + pC = " int "; + break; + case 0x222c: + pC = " iint "; + break; + case 0x222d: + pC = " iiint "; + break; + case 0x222e: + pC = " lint "; + break; + case 0x222f: + pC = " llint "; + break; + case 0x2230: + pC = " lllint "; + break; + case 0x2245: + pC = " simeq "; + break; + case 0x2248: + pC = " approx "; + break; + case 0x2260: + pC = " <> "; + break; + case 0x2261: + pC = " equiv "; + break; + case 0x2264: + pC = " <= "; + break; + case 0x2265: + pC = " >= "; + break; + + case 0x227A: + pC = " prec "; + break; + case 0x227B: + pC = " succ "; + break; + case 0x227C: + pC = " preccurlyeq "; + break; + case 0x227D: + pC = " succcurlyeq "; + break; + case 0x227E: + pC = " precsim "; + break; + case 0x227F: + pC = " succsim "; + break; + case 0x2280: + pC = " nprec "; + break; + case 0x2281: + pC = " nsucc "; + break; + + case 0x2282: // subset + case 0x2283: // supset + case 0x2284: // nsubset + case 0x2285: // nsupset + case 0x2286: // subseteq + case 0x2287: // supseteq + case 0x2288: // nsubseteq + case 0x2289: // nsupseteq + case 0x22b2: // NORMAL SUBGROUP OF + case 0x22b3: // CONTAINS AS NORMAL SUBGROUP + rRet.append(" func " + OUStringChar(nChar) + " "); + break; + case 0x22a5: + pC = " ortho "; + break; + case 0x22c5: + pC = " cdot "; + break; + case 0x22ee: + pC = " dotsvert "; + break; + case 0x22ef: + pC = " dotsaxis "; + break; + case 0x22f0: + pC = " dotsup "; + break; + case 0x22f1: + pC = " dotsdown "; + break; + case MS_LANGLE: + case MS_LMATHANGLE: + pC = " langle "; + break; + case MS_RANGLE: + case MS_RMATHANGLE: + pC = " rangle "; + break; + case 0x301a: + pC = " ldbracket "; + break; + case 0x301b: + pC = " rdbracket "; + break; + case 0xe083: + rRet.append("+"); + bRet=true; + break; + case '^': + case 0xe091: + pC = " widehat "; + break; + case 0xe096: + pC = " widetilde "; + break; + case 0xe098: + pC = " widevec "; + break; + case 0xE421: + pC = " geslant "; + break; + case 0xE425: + pC = " leslant "; + break; + case 0xeb01: //no space + case 0xeb08: //normal space + bRet=true; + break; + case 0xef04: //tiny space + case 0xef05: //tiny space + case 0xeb02: //small space + case 0xeb04: //medium space + rRet.append("`"); + break; + case 0xeb05: //large space + rRet.append("~"); + break; + case 0x3a9: + pC = " %OMEGA "; + break; + default: + rRet.append(OUStringChar(nChar)); + bRet=true; + break; + } + if (pC) + rRet.appendAscii(pC); + return bRet; +} + +void MathTypeFont::AppendStyleToText(OUString &rRet) +{ + const char *pC = nullptr; + switch (nStyle) + { + default: + case 0: + break; + case 1: + pC = " ital "; + break; + case 2: + pC = " bold "; + break; + case 3: + pC = " bold italic"; + break; + } + if (pC) + rRet += OUString::createFromAscii( pC ); +} + +void MathType::TypeFaceToString(OUString &rTxt,sal_uInt8 nFace) +{ + MathTypeFont aFont(nFace); + auto aItr = aUserStyles.find(aFont); + if (aItr != aUserStyles.end()) + aFont.nStyle = aItr->nStyle; + aFont.AppendStyleToText(rTxt); +} + +bool MathType::Parse(SotStorage *pStor) +{ + tools::SvRef<SotStorageStream> xSrc = pStor->OpenSotStream( + "Equation Native", + StreamMode::STD_READ); + if ( (!xSrc.is()) || (ERRCODE_NONE != xSrc->GetError())) + return false; + return Parse(xSrc.get()); +} + +bool MathType::Parse(SvStream* pStream) +{ + pS = pStream; + pS->SetEndian( SvStreamEndian::LITTLE ); + + EQNOLEFILEHDR aHdr; + aHdr.Read(pS); + sal_uInt8 nProdVersion; + sal_uInt8 nProdSubVersion; + sal_uInt8 nPlatform; + sal_uInt8 nProduct; + pS->ReadUChar( nVersion ); + pS->ReadUChar( nPlatform ); + pS->ReadUChar( nProduct ); + pS->ReadUChar( nProdVersion ); + pS->ReadUChar( nProdSubVersion ); + + if (!pS->good() || nVersion > 3) // allow only supported versions of MathType to be parsed + return false; + + bool bRet = HandleRecords(0); + //little crude hack to close occasionally open expressions + //a sophisticated system to determine what expressions are + //opened is required, but this is as much work as rewriting + //starmaths internals. + rRet.append("{}"); + + return bRet; +} + +static void lcl_PrependDummyTerm(OUStringBuffer &rRet, sal_Int32 &rTextStart) +{ + if ((rTextStart < rRet.getLength()) && + (rRet[rTextStart] == '=') && + ((rTextStart == 0) || (rRet[ rTextStart-1 ] == '{')) + ) + { + rRet.insert(rTextStart, " {}"); + rTextStart+=3; + } +} + +static void lcl_AppendDummyTerm(OUStringBuffer &rRet) +{ + bool bOk=false; + for(int nI=rRet.getLength()-1;nI >= 0; nI--) + { + sal_Int32 nIdx = sal::static_int_cast< sal_Int32 >(nI); + sal_Unicode nChar = rRet[nIdx]; + if (nChar == ' ') + continue; + if (rRet[nIdx] != '{') + bOk=true; + break; + } + if (!bOk) //No term, use dummy + rRet.append(" {}"); +} + +void MathType::HandleNudge() +{ + sal_uInt8 nXNudge(0); + pS->ReadUChar(nXNudge); + sal_uInt8 nYNudge(0); + pS->ReadUChar(nYNudge); + if (nXNudge == 128 && nYNudge == 128) + { + sal_uInt16 nXLongNudge(0); + sal_uInt16 nYLongNudge(0); + pS->ReadUInt16(nXLongNudge); + pS->ReadUInt16(nYLongNudge); + } +} + +/* Fabulously complicated as many tokens have to be reordered and generally + * moved around from mathtypes paradigm to starmaths. */ +bool MathType::HandleRecords(int nLevel, sal_uInt8 nSelector, + sal_uInt8 nVariation, int nMatrixRows, int nMatrixCols) +{ + //depth-protect + if (nLevel > 1024) + return false; + + sal_uInt8 nTag,nRecord; + sal_uInt8 nTabType; + sal_uInt16 nTabOffset; + int i, newline=0; + bool bSilent=false; + int nPart=0; + OUString sPush,sMainTerm; + int nSetSize=0,nSetAlign=0; + int nCurRow=0,nCurCol=0; + bool bOpenString=false; + sal_Int32 nTextStart = 0; + sal_Int32 nSubSupStartPos = 0; + sal_Int32 nLastTemplateBracket=-1; + bool bRet = true; + + do + { + nTag = 0; + pS->ReadUChar( nTag ); + nRecord = nTag&0x0F; + + /*MathType strings can of course include words which + *are StarMath keywords, the simplest solution is + to escape strings of greater than len 1 with double + quotes to avoid scanning the TokenTable for matches + + Unfortunately it may turn out that the string gets + split during the handling of a character emblishment + so this special case must be handled in the + character handler case 2: + */ + if ((nRecord == CHAR) && (!bOpenString)) + { + bOpenString=true; + nTextStart = rRet.getLength(); + } + else if ((nRecord != CHAR) && bOpenString) + { + bOpenString=false; + if ((rRet.getLength() - nTextStart) > 1) + { + OUString aStr; + TypeFaceToString(aStr,nTypeFace); + rRet.insert(nTextStart, aStr + "\""); + rRet.append("\""); + } + else if (nRecord == END && !rRet.isEmpty()) + { + sal_Unicode cChar = 0; + sal_Int32 nI = rRet.getLength()-1; + while (nI) + { + cChar = rRet[nI]; + if (cChar != ' ') + break; + --nI; + } + if ((cChar == '=') || (cChar == '+') || (cChar == '-')) + rRet.append("{}"); + } + } + + switch(nRecord) + { + case LINE: + { + if (xfLMOVE(nTag)) + HandleNudge(); + + if (newline>0) + rRet.append("\nnewline\n"); + if (!(xfNULL(nTag))) + { + switch (nSelector) + { + case tmANGLE: + if (nVariation==0) + rRet.append(" langle "); + else if (nVariation==1) + rRet.append(" \\langle "); + break; + case tmPAREN: + if (nVariation==0) + rRet.append(" left ("); + else if (nVariation==1) + rRet.append("\\("); + break; + case tmBRACE: + if ((nVariation==0) || (nVariation==1)) + rRet.append(" left lbrace "); + else + rRet.append(" left none "); + break; + case tmBRACK: + if (nVariation==0) + rRet.append(" left ["); + else if (nVariation==1) + rRet.append("\\["); + break; + case tmLBLB: + case tmLBRP: + rRet.append(" \\["); + break; + case tmBAR: + if (nVariation==0) + rRet.append(" lline "); + else if (nVariation==1) + rRet.append(" \\lline "); + break; + case tmDBAR: + if (nVariation==0) + rRet.append(" ldline "); + else if (nVariation==1) + rRet.append(" \\ldline "); + break; + case tmFLOOR: + if (nVariation == 0 || nVariation & 0x01) // tvFENCE_L + rRet.append(" left lfloor "); + else + rRet.append(" left none "); + break; + case tmCEILING: + if (nVariation==0) + rRet.append(" lceil "); + else if (nVariation==1) + rRet.append(" \\lceil "); + break; + case tmRBRB: + case tmRBLB: + rRet.append(" \\]"); + break; + case tmLPRB: + rRet.append(" \\("); + break; + case tmROOT: + if (nPart == 0) + { + if (nVariation == 0) + rRet.append(" sqrt"); + else + { + rRet.append(" nroot"); + sPush = rRet.makeStringAndClear(); + } + } + rRet.append(" {"); + break; + case tmFRACT: + if (nPart == 0) + rRet.append(" { "); + + + if (nPart == 1) + rRet.append(" over "); + rRet.append(" {"); + break; + case tmSCRIPT: + nSubSupStartPos = rRet.getLength(); + if ((nVariation == 0) || + ((nVariation == 2) && (nPart==1))) + { + lcl_AppendDummyTerm(rRet); + rRet.append(" rSup"); + } + else if ((nVariation == 1) || + ((nVariation == 2) && (nPart==0))) + { + lcl_AppendDummyTerm(rRet); + rRet.append(" rSub"); + } + rRet.append(" {"); + break; + case tmUBAR: + if (nVariation == 0) + rRet.append(" {underline "); + else if (nVariation == 1) + rRet.append(" {underline underline "); + rRet.append(" {"); + break; + case tmOBAR: + if (nVariation == 0) + rRet.append(" {overline "); + else if (nVariation == 1) + rRet.append(" {overline overline "); + rRet.append(" {"); + break; + case tmLARROW: + if (nPart == 0) + { + if (nVariation == 0) + rRet.append(" widevec ");//left arrow above + else if (nVariation == 1) + rRet.append(" widevec ");//left arrow below + rRet.append(" {"); + } + break; + case tmRARROW: + if (nPart == 0) + { + if (nVariation == 0) + rRet.append(" widevec ");//right arrow above + else if (nVariation == 1) + rRet.append(" widevec ");//right arrow below + rRet.append(" {"); + } + break; + case tmBARROW: + if (nPart == 0) + { + if (nVariation == 0) + rRet.append(" widevec ");//double arrow above + else if (nVariation == 1) + rRet.append(" widevec ");//double arrow below + rRet.append(" {"); + } + break; + case tmSINT: + if (nPart == 0) + { + if ((nVariation == 3) || (nVariation == 4)) + rRet.append(" lInt"); + else + rRet.append(" Int"); + if ( (nVariation != 0) && (nVariation != 3)) + { + sPush = rRet.makeStringAndClear(); + } + } + if (((nVariation == 1) || + (nVariation == 4)) && (nPart==1)) + rRet.append(" rSub"); + else if ((nVariation == 2) && (nPart==2)) + rRet.append(" rSup"); + else if ((nVariation == 2) && (nPart==1)) + rRet.append(" rSub"); + rRet.append(" {"); + break; + case tmDINT: + if (nPart == 0) + { + if ((nVariation == 2) || (nVariation == 3)) + rRet.append(" llInt"); + else + rRet.append(" iInt"); + if ( (nVariation != 0) && (nVariation != 2)) + { + sPush = rRet.makeStringAndClear(); + } + } + if (((nVariation == 1) || + (nVariation == 3)) && (nPart==1)) + rRet.append(" rSub"); + rRet.append(" {"); + break; + case tmTINT: + if (nPart == 0) + { + if ((nVariation == 2) || (nVariation == 3)) + rRet.append(" lllInt"); + else + rRet.append(" iiInt"); + if ( (nVariation != 0) && (nVariation != 2)) + { + sPush = rRet.makeStringAndClear(); + } + } + if (((nVariation == 1) || + (nVariation == 3)) && (nPart==1)) + rRet.append(" rSub"); + rRet.append(" {"); + break; + case tmSSINT: + if (nPart == 0) + { + if (nVariation == 2) + rRet.append(" lInt"); + else + rRet.append(" Int"); + sPush = rRet.makeStringAndClear(); + } + if (((nVariation == 1) || + (nVariation == 2)) && (nPart==1)) + rRet.append(" cSub"); + else if ((nVariation == 0) && (nPart==2)) + rRet.append(" cSup"); + else if ((nVariation == 0) && (nPart==1)) + rRet.append(" cSub"); + rRet.append(" {"); + break; + case tmDSINT: + if (nPart == 0) + { + if (nVariation == 0) + rRet.append(" llInt"); + else + rRet.append(" iInt"); + sPush = rRet.makeStringAndClear(); + } + if (nPart==1) + rRet.append(" cSub"); + rRet.append(" {"); + break; + case tmTSINT: + if (nPart == 0) + { + if (nVariation == 0) + rRet.append(" lllInt"); + else + rRet.append(" iiInt"); + sPush = rRet.makeStringAndClear(); + } + if (nPart==1) + rRet.append(" cSub"); + rRet.append(" {"); + break; + case tmUHBRACE: + case tmLHBRACE: + rRet.append(" {"); + break; + case tmSUM: + if (nPart == 0) + { + rRet.append(" Sum"); + if (nVariation != 2) + { + sPush = rRet.makeStringAndClear(); + } + } + if ((nVariation == 0) && (nPart==1)) + rRet.append(" cSub"); + else if ((nVariation == 1) && (nPart==2)) + rRet.append(" cSup"); + else if ((nVariation == 1) && (nPart==1)) + rRet.append(" cSub"); + rRet.append(" {"); + break; + case tmISUM: + if (nPart == 0) + { + rRet.append(" Sum"); + sPush = rRet.makeStringAndClear(); + } + if ((nVariation == 0) && (nPart==1)) + rRet.append(" rSub"); + else if ((nVariation == 1) && (nPart==2)) + rRet.append(" rSup"); + else if ((nVariation == 1) && (nPart==1)) + rRet.append(" rSub"); + rRet.append(" {"); + break; + case tmPROD: + if (nPart == 0) + { + rRet.append(" Prod"); + if (nVariation != 2) + { + sPush = rRet.makeStringAndClear(); + } + } + if ((nVariation == 0) && (nPart==1)) + rRet.append(" cSub"); + else if ((nVariation == 1) && (nPart==2)) + rRet.append(" cSup"); + else if ((nVariation == 1) && (nPart==1)) + rRet.append(" cSub"); + rRet.append(" {"); + break; + case tmIPROD: + if (nPart == 0) + { + rRet.append(" Prod"); + sPush = rRet.makeStringAndClear(); + } + if ((nVariation == 0) && (nPart==1)) + rRet.append(" rSub"); + else if ((nVariation == 1) && (nPart==2)) + rRet.append(" rSup"); + else if ((nVariation == 1) && (nPart==1)) + rRet.append(" rSub"); + rRet.append(" {"); + break; + case tmCOPROD: + if (nPart == 0) + { + rRet.append(" coProd"); + if (nVariation != 2) + { + sPush = rRet.makeStringAndClear(); + } + } + if ((nVariation == 0) && (nPart==1)) + rRet.append(" cSub"); + else if ((nVariation == 1) && (nPart==2)) + rRet.append(" cSup"); + else if ((nVariation == 1) && (nPart==1)) + rRet.append(" cSub"); + rRet.append(" {"); + break; + case tmICOPROD: + if (nPart == 0) + { + rRet.append(" coProd"); + sPush = rRet.makeStringAndClear(); + } + if ((nVariation == 0) && (nPart==1)) + rRet.append(" rSub"); + else if ((nVariation == 1) && (nPart==2)) + rRet.append(" rSup"); + else if ((nVariation == 1) && (nPart==1)) + rRet.append(" rSub"); + rRet.append(" {"); + break; + case tmUNION: + if (nPart == 0) + { + rRet.append(" union"); //union + if (nVariation != 2) + { + sPush = rRet.makeStringAndClear(); + } + } + if ((nVariation == 0) && (nPart==1)) + rRet.append(" cSub"); + else if ((nVariation == 1) && (nPart==2)) + rRet.append(" cSup"); + else if ((nVariation == 1) && (nPart==1)) + rRet.append(" cSub"); + rRet.append(" {"); + break; + case tmIUNION: + if (nPart == 0) + { + rRet.append(" union"); //union + sPush = rRet.makeStringAndClear(); + } + if ((nVariation == 0) && (nPart==1)) + rRet.append(" rSub"); + else if ((nVariation == 1) && (nPart==2)) + rRet.append(" rSup"); + else if ((nVariation == 1) && (nPart==1)) + rRet.append(" rSub"); + rRet.append(" {"); + break; + case tmINTER: + if (nPart == 0) + { + rRet.append(" intersect"); //intersect + if (nVariation != 2) + { + sPush = rRet.makeStringAndClear(); + } + } + if ((nVariation == 0) && (nPart==1)) + rRet.append(" cSub"); + else if ((nVariation == 1) && (nPart==2)) + rRet.append(" cSup"); + else if ((nVariation == 1) && (nPart==1)) + rRet.append(" cSub"); + rRet.append(" {"); + break; + case tmIINTER: + if (nPart == 0) + { + rRet.append(" intersect"); //intersect + sPush = rRet.makeStringAndClear(); + } + if ((nVariation == 0) && (nPart==1)) + rRet.append(" rSub"); + else if ((nVariation == 1) && (nPart==2)) + rRet.append(" rSup"); + else if ((nVariation == 1) && (nPart==1)) + rRet.append(" rSub"); + rRet.append(" {"); + break; + case tmLIM: + if ((nVariation == 0) && (nPart==1)) + rRet.append(" cSup"); + else if ((nVariation == 1) && (nPart==1)) + rRet.append(" cSub"); + else if ((nVariation == 2) && (nPart==1)) + rRet.append(" cSub"); + else if ((nVariation == 2) && (nPart==2)) + rRet.append(" cSup"); + rRet.append(" {"); + break; + case tmLDIV: + if (nVariation == 0) + { + if (nPart == 0) + { + sPush = rRet.makeStringAndClear(); + } + } + rRet.append(" {"); + if (nVariation == 0) + { + if (nPart == 1) + rRet.append("alignr "); + } + if (nPart == 0) + rRet.append("\\lline "); + if (nVariation == 1) + rRet.append("overline "); + break; + case tmSLFRACT: + rRet.append(" {"); + break; + case tmINTOP: + if (nPart == 0) + { + sPush = rRet.makeStringAndClear(); + } + if ((nVariation == 0) && (nPart==0)) + rRet.append(" rSup"); + else if ((nVariation == 2) && (nPart==1)) + rRet.append(" rSup"); + else if ((nVariation == 1) && (nPart==0)) + rRet.append(" rSub"); + else if ((nVariation == 2) && (nPart==0)) + rRet.append(" rSub"); + rRet.append(" {"); + break; + case tmSUMOP: + if (nPart == 0) + { + sPush = rRet.makeStringAndClear(); + } + if ((nVariation == 0) && (nPart==0)) + rRet.append(" cSup"); + else if ((nVariation == 2) && (nPart==1)) + rRet.append(" cSup"); + else if ((nVariation == 1) && (nPart==0)) + rRet.append(" cSub"); + else if ((nVariation == 2) && (nPart==0)) + rRet.append(" cSub"); + rRet.append(" {"); + break; + case tmLSCRIPT: + if (nPart == 0) + rRet.append("\"\""); + if ((nVariation == 0) + || ((nVariation == 2) && (nPart==1))) + rRet.append(" lSup"); + else if ((nVariation == 1) + || ((nVariation == 2) && (nPart==0))) + rRet.append(" lSub"); + rRet.append(" {"); + break; + case tmDIRAC: + if (nVariation==0) + { + if (nPart == 0) + rRet.append(" langle "); + } + else if (nVariation==1) + { + rRet.append(" \\langle "); + newline--; + } + else if (nVariation==2) + { + rRet.append(" \\lline "); + newline--; + } + break; + case tmUARROW: + if (nVariation == 0) + rRet.append(" widevec ");//left below + else if (nVariation == 1) + rRet.append(" widevec ");//right below + else if (nVariation == 2) + rRet.append(" widevec ");//double headed below + rRet.append(" {"); + break; + case tmOARROW: + if (nVariation == 0) + rRet.append(" widevec ");//left above + else if (nVariation == 1) + rRet.append(" widevec ");//right above + else if (nVariation == 2) + rRet.append(" widevec ");//double headed above + rRet.append(" {"); + break; + default: + break; + } + sal_Int16 nOldCurSize=nCurSize; + sal_Int32 nSizeStartPos = rRet.getLength(); + HandleSize( nLSize, nDSize, nSetSize ); + bRet = HandleRecords( nLevel+1 ); + while (nSetSize) + { + bool bOk=false; + sal_Int32 nI = rRet.lastIndexOf('{'); + if (nI != -1) + { + for(nI=nI+1;nI<rRet.getLength();nI++) + if (rRet[nI] != ' ') + { + bOk=true; + break; + } + } + else + bOk=true; + + if (bOk) + rRet.append("} "); + else if (rRet.getLength() > nSizeStartPos) + rRet = rRet.truncate(nSizeStartPos); + nSetSize--; + nCurSize=nOldCurSize; + } + + + HandleMatrixSeparator(nMatrixRows,nMatrixCols, + nCurCol,nCurRow); + + switch (nSelector) + { + case tmANGLE: + if (nVariation==0) + rRet.append(" rangle "); + else if (nVariation==2) + rRet.append(" \\rangle "); + break; + case tmPAREN: + if (nVariation==0) + rRet.append(" right )"); + else if (nVariation==2) + rRet.append("\\)"); + break; + case tmBRACE: + if ((nVariation==0) || (nVariation==2)) + rRet.append(" right rbrace "); + else + rRet.append(" right none "); + break; + case tmBRACK: + if (nVariation==0) + rRet.append(" right ]"); + else if (nVariation==2) + rRet.append("\\]"); + break; + case tmBAR: + if (nVariation==0) + rRet.append(" rline "); + else if (nVariation==2) + rRet.append(" \\rline "); + break; + case tmDBAR: + if (nVariation==0) + rRet.append(" rdline "); + else if (nVariation==2) + rRet.append(" \\rdline "); + break; + case tmFLOOR: + if (nVariation == 0 || nVariation & 0x02) // tvFENCE_R + rRet.append(" right rfloor "); + else + rRet.append(" right none "); + break; + case tmCEILING: + if (nVariation==0) + rRet.append(" rceil "); + else if (nVariation==2) + rRet.append(" \\rceil "); + break; + case tmLBLB: + case tmRBLB: + rRet.append("\\["); + break; + case tmRBRB: + case tmLPRB: + rRet.append("\\]"); + break; + case tmROOT: + rRet.append("} "); + if (nVariation == 1) + { + if (nPart == 0) + { + newline--; + sMainTerm = rRet.makeStringAndClear(); + } + else if (nPart == 1) + { + rRet.insert(0, sPush); + rRet.append(sMainTerm); + sPush.clear(); + sMainTerm.clear(); + } + } + else + { + if (nPart == 0) + newline--; + } + nPart++; + break; + case tmLBRP: + rRet.append("\\)"); + break; + case tmFRACT: + rRet.append("} "); + if (nPart == 0) + newline--; + else + rRet.append("} "); + nPart++; + break; + case tmSCRIPT: + { + if ((nPart == 0) && + ((nVariation == 2) || (nVariation == 1))) + newline--; + + bool bOk=false; + sal_Int32 nI = rRet.lastIndexOf('{'); + if (nI != -1) + { + for(nI=nI+1;nI<rRet.getLength();nI++) + if (rRet[nI] != ' ') + { + bOk=true; + break; + } + } + else + bOk=true; + + if (bOk) + rRet.append("} "); + else if (rRet.getLength() > nSubSupStartPos) + rRet = rRet.truncate(nSubSupStartPos); + nPart++; + } + break; + case tmLSCRIPT: + if ((nPart == 0) && + ((nVariation == 2) || (nVariation == 1))) + newline--; + rRet.append("} "); + nPart++; + break; + case tmUARROW: + case tmOARROW: + rRet.append("} "); + break; + case tmUBAR: + case tmOBAR: + rRet.append("}} "); + break; + case tmLARROW: + case tmRARROW: + case tmBARROW: + if (nPart == 0) + { + newline--; + rRet.append("} "); + } + nPart++; + break; + case tmUHBRACE: + rRet.append("} "); + if (nPart == 0) + { + newline--; + rRet.append("overbrace"); + } + nPart++; + break; + case tmLHBRACE: + rRet.append("} "); + if (nPart == 0) + { + newline--; + rRet.append("underbrace"); + } + nPart++; + break; + case tmLIM: + if (nPart==0) + newline--; + else if ((nPart==1) && + ((nVariation == 2) || (nVariation == 1))) + newline--; + rRet.append("} "); + nPart++; + break; + case tmLDIV: + rRet.append("} "); + if (nVariation == 0) + { + if (nPart == 0) + { + sMainTerm = rRet.makeStringAndClear(); + } + else if (nPart == 1) + { + rRet.insert(0, sPush); + rRet.append(" over " + sMainTerm); + sPush.clear(); + sMainTerm.clear(); + } + } + if (nPart == 0) + newline--; + nPart++; + break; + case tmSLFRACT: + rRet.append("} "); + if (nPart == 0) + { + newline--; + switch (nVariation) + { + case 1: + rRet.append("slash"); + break; + default: + rRet.append("wideslash"); + break; + } + } + nPart++; + break; + case tmSUM: + case tmISUM: + case tmPROD: + case tmIPROD: + case tmCOPROD: + case tmICOPROD: + case tmUNION: + case tmIUNION: + case tmINTER: + case tmIINTER: + rRet.append("} "); + if (nPart == 0) + { + if (nVariation != 2) + { + sMainTerm = rRet.makeStringAndClear(); + } + newline--; + } + else if ((nPart == 1) && (nVariation == 0)) + { + rRet.insert(0, sPush); + rRet.append(sMainTerm); + sPush.clear(); + sMainTerm.clear(); + newline--; + } + else if ((nPart == 1) && (nVariation == 1)) + newline--; + else if ((nPart == 2) && (nVariation == 1)) + { + rRet.insert(0, sPush); + rRet.append(sMainTerm); + sPush.clear(); + sMainTerm.clear(); + newline--; + } + nPart++; + break; + case tmSINT: + rRet.append("} "); + if (nPart == 0) + { + if ((nVariation != 0) && (nVariation != 3)) + { + sMainTerm = rRet.makeStringAndClear(); + } + newline--; + } + else if ((nPart == 1) && + ((nVariation == 1) || (nVariation==4))) + { + rRet.insert(0, sPush); + rRet.append(sMainTerm); + sPush.clear(); + sMainTerm.clear(); + newline--; + } + else if ((nPart == 1) && (nVariation == 2)) + newline--; + else if ((nPart == 2) && (nVariation == 2)) + { + rRet.insert(0, sPush); + rRet.append(sMainTerm); + sPush.clear(); + sMainTerm.clear(); + newline--; + } + nPart++; + break; + case tmDINT: + case tmTINT: + rRet.append("} "); + if (nPart == 0) + { + if ((nVariation != 0) && (nVariation != 2)) + { + sMainTerm = rRet.makeStringAndClear(); + } + newline--; + } + else if ((nPart == 1) && + ((nVariation == 1) || (nVariation==3))) + { + rRet.insert(0, sPush); + rRet.append(sMainTerm); + sPush.clear(); + sMainTerm.clear(); + newline--; + } + nPart++; + break; + case tmSSINT: + rRet.append("} "); + if (nPart == 0) + { + sMainTerm = rRet.makeStringAndClear(); + newline--; + } + else if ((nPart == 1) && + ((nVariation == 1) || (nVariation==2))) + { + rRet.insert(0, sPush); + rRet.append(sMainTerm); + sPush.clear(); + sMainTerm.clear(); + newline--; + } + else if ((nPart == 1) && (nVariation == 0)) + newline--; + else if ((nPart == 2) && (nVariation == 0)) + { + rRet.insert(0, sPush); + rRet.append(sMainTerm); + sPush.clear(); + sMainTerm.clear(); + newline--; + } + nPart++; + break; + case tmDSINT: + case tmTSINT: + rRet.append("} "); + if (nPart == 0) + { + sMainTerm = rRet.makeStringAndClear(); + newline--; + } + else if (nPart == 1) + { + rRet.insert(0, sPush); + rRet.append(sMainTerm); + sPush.clear(); + sMainTerm.clear(); + newline--; + } + nPart++; + break; + case tmINTOP: + case tmSUMOP: + rRet.append("} "); + + if ((nPart == 0) && + ((nVariation == 0) || (nVariation == 1))) + { + sMainTerm = rRet.makeStringAndClear(); + newline--; + } + else if ((nPart == 0) && (nVariation == 2)) + newline--; + else if ((nPart == 1) && (nVariation == 2)) + { + sMainTerm = rRet.makeStringAndClear(); + newline--; + } + else if ((nPart == 2) || ((nPart == 1) && + (nVariation == 0 || nVariation == 1))) + { + rRet.insert(0, sPush); + rRet.append(sMainTerm); + sPush.clear(); + sMainTerm.clear(); + } + nPart++; + break; + case tmDIRAC: + if (nVariation==0) + { + if (nPart == 0) + { + newline--; //there is another term to arrive + rRet.append(" mline "); + } + else + rRet.append(" rangle "); + } + else if (nVariation==1) + rRet.append(" \\lline "); + else if (nVariation==2) + rRet.append(" \\rangle "); + nPart++; + break; + default: + break; + } + bSilent = true; //Skip the optional brackets and/or + //symbols that follow some of these + //records. Foo Data. + + /*In matrices and piles we cannot separate equation + *lines with the newline keyword*/ + if (nMatrixCols==0) + newline++; + } + } + break; + case CHAR: + if (xfLMOVE(nTag)) + HandleNudge(); + bRet = HandleChar( nTextStart, nSetSize, nLevel, nTag, nSelector, nVariation, bSilent ); + break; + case TMPL: + if (xfLMOVE(nTag)) + HandleNudge(); + bRet = HandleTemplate( nLevel, nSelector, nVariation, nLastTemplateBracket ); + break; + case PILE: + if (xfLMOVE(nTag)) + HandleNudge(); + bRet = HandlePile( nSetAlign, nLevel, nSelector, nVariation ); + HandleMatrixSeparator( nMatrixRows, nMatrixCols, nCurCol, nCurRow ); + break; + case MATRIX: + if (xfLMOVE(nTag)) + HandleNudge(); + bRet = HandleMatrix( nLevel, nSelector, nVariation ); + HandleMatrixSeparator( nMatrixRows, nMatrixCols, nCurCol, nCurRow ); + break; + case EMBEL: + if (xfLMOVE(nTag)) + HandleNudge(); + HandleEmblishments(); + break; + case RULER: + { + sal_uInt8 nTabStops(0); + pS->ReadUChar( nTabStops ); + for (i=0;i<nTabStops;i++) + { + pS->ReadUChar( nTabType ); + pS->ReadUInt16( nTabOffset ); + } + SAL_WARN("starmath", "Not seen in the wild Equation Ruler Field"); + break; + } + case FONT: + { + MathTypeFont aFont; + pS->ReadUChar( aFont.nTface ); + /* + The typeface number is the negative (which makes it + positive) of the typeface value (unbiased) that appears in + CHAR records that might follow a given FONT record + */ + aFont.nTface = 128-aFont.nTface; + pS->ReadUChar( aFont.nStyle ); + aUserStyles.insert(aFont); + // read font name + while(true) + { + char nChar8(0); + pS->ReadChar( nChar8 ); + if (nChar8 == 0) + break; + } + } + break; + case SIZE: + HandleSetSize(); + break; + case 10: + case 11: + case 12: + case 13: + case 14: + nLSize=nRecord-10; + break; + case END: + default: + break; + } + } + while (nRecord != END && !pS->eof()); + while (nSetSize) + { + rRet.append("}"); + nSetSize--; + } + return bRet; +} + +/*Simply determine if we are at the end of a record or the end of a line, + *with fiddly logic to see if we are in a matrix or a pile or neither + + Note we cannot tell until after the event that this is the last entry + of a pile, so we must strip the last separator of a pile after this + is detected in the PILE handler + */ +void MathType::HandleMatrixSeparator(int nMatrixRows,int nMatrixCols, + int &rCurCol,int &rCurRow) +{ + if (nMatrixRows==0) + return; + + if (rCurCol == nMatrixCols-1) + { + if (rCurRow != nMatrixRows-1) + rRet.append(" {} ##\n"); + if (nMatrixRows!=-1) + { + rCurCol=0; + rCurRow++; + } + } + else + { + rRet.append(" {} # "); + if (nMatrixRows!=-1) + rCurCol++; + else + rRet.append("\n"); + } +} + +/* set the alignment of the following term, but starmath currently + * cannot handle vertical alignment */ +void MathType::HandleAlign(sal_uInt8 nHorAlign, int &rSetAlign) +{ + switch(nHorAlign) + { + case 1: + default: + rRet.append("alignl {"); + break; + case 2: + rRet.append("alignc {"); + break; + case 3: + rRet.append("alignr {"); + break; + } + rSetAlign++; +} + +/* set size of text, complexity due to overuse of signedness as a flag + * indicator by mathtype file format*/ +bool MathType::HandleSize(sal_Int16 nLstSize,sal_Int16 nDefSize, int &rSetSize) +{ + bool bRet=false; + if (nLstSize < 0) + { + const sal_Int16 nDefaultSize = 12; + if ((-nLstSize/32 != nDefaultSize) && (-nLstSize/32 != nCurSize)) + { + if (rSetSize) + { + rSetSize--; + rRet.append("}"); + bRet=true; + } + if (-nLstSize/32 != nLastSize) + { + nLastSize = nCurSize; + rRet.append(" size "); + rRet.append(static_cast<sal_Int32>(-nLstSize/32)); + rRet.append("{"); + bRet=true; + rSetSize++; + } + nCurSize = -nLstSize/32; + } + } + else + { + /*sizetable should theoretically be filled with the default sizes + *of the various font groupings matching starmaths equivalents + in aTypeFaces, and a test would be done to see if the new font + size would be the same as what starmath would have chosen for + itself anyway in which case the size setting could be ignored*/ + nLstSize = aSizeTable.at(nLstSize); + nLstSize = nLstSize + nDefSize; + if (nLstSize != nCurSize) + { + if (rSetSize) + { + rSetSize--; + rRet.append("}"); + bRet=true; + } + if (nLstSize != nLastSize) + { + nLastSize = nCurSize; + rRet.append(" size "); + rRet.append(static_cast<sal_Int32>(nLstSize)); + rRet.append("{"); + bRet=true; + rSetSize++; + } + nCurSize = nLstSize; + } + } + return bRet; +} + +bool MathType::ConvertFromStarMath( SfxMedium& rMedium ) +{ + if (!pTree) + return false; + + SvStream *pStream = rMedium.GetOutStream(); + if ( pStream ) + { + tools::SvRef<SotStorage> pStor = new SotStorage( pStream, false ); + + SvGlobalName aGName(MSO_EQUATION3_CLASSID); + pStor->SetClass( aGName, SotClipboardFormatId::NONE, "Microsoft Equation 3.0"); + + static sal_uInt8 const aCompObj[] = { + 0x01, 0x00, 0xFE, 0xFF, 0x03, 0x0A, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0x02, 0xCE, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x46, 0x17, 0x00, 0x00, 0x00, + 0x4D, 0x69, 0x63, 0x72, 0x6F, 0x73, 0x6F, 0x66, + 0x74, 0x20, 0x45, 0x71, 0x75, 0x61, 0x74, 0x69, + 0x6F, 0x6E, 0x20, 0x33, 0x2E, 0x30, 0x00, 0x0C, + 0x00, 0x00, 0x00, 0x44, 0x53, 0x20, 0x45, 0x71, + 0x75, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x00, 0x0B, + 0x00, 0x00, 0x00, 0x45, 0x71, 0x75, 0x61, 0x74, + 0x69, 0x6F, 0x6E, 0x2E, 0x33, 0x00, 0xF4, 0x39, + 0xB2, 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + tools::SvRef<SotStorageStream> xStor( pStor->OpenSotStream("\1CompObj")); + xStor->WriteBytes(aCompObj, sizeof(aCompObj)); + + static sal_uInt8 const aOle[] = { + 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 + }; + tools::SvRef<SotStorageStream> xStor2( pStor->OpenSotStream("\1Ole")); + xStor2->WriteBytes(aOle, sizeof(aOle)); + xStor.clear(); + xStor2.clear(); + + tools::SvRef<SotStorageStream> xSrc = pStor->OpenSotStream("Equation Native"); + if ( (!xSrc.is()) || (ERRCODE_NONE != xSrc->GetError())) + return false; + + pS = xSrc.get(); + pS->SetEndian( SvStreamEndian::LITTLE ); + + pS->SeekRel(EQNOLEFILEHDR_SIZE); //Skip 28byte Header and fill it in later + pS->WriteUChar( 0x03 ); + pS->WriteUChar( 0x01 ); + pS->WriteUChar( 0x01 ); + pS->WriteUChar( 0x03 ); + pS->WriteUChar( 0x00 ); + sal_uInt64 nSize = pS->Tell(); + nPendingAttributes=0; + + HandleNodes(pTree, 0); + pS->WriteUChar( END ); + + nSize = pS->Tell()-nSize; + pS->Seek(0); + EQNOLEFILEHDR aHdr(nSize+4+1); + aHdr.Write(pS); + + pStor->Commit(); + } + + return true; +} + + +void MathType::HandleNodes(SmNode *pNode,int nLevel) +{ + switch(pNode->GetType()) + { + case SmNodeType::Attribute: + HandleAttributes(pNode,nLevel); + break; + case SmNodeType::Text: + HandleText(pNode); + break; + case SmNodeType::VerticalBrace: + HandleVerticalBrace(pNode,nLevel); + break; + case SmNodeType::Brace: + HandleBrace(pNode,nLevel); + break; + case SmNodeType::Oper: + HandleOperator(pNode,nLevel); + break; + case SmNodeType::BinVer: + HandleFractions(pNode,nLevel); + break; + case SmNodeType::Root: + HandleRoot(pNode,nLevel); + break; + case SmNodeType::Special: + { + SmTextNode *pText = static_cast<SmTextNode *>(pNode); + //if the token str and the result text are the same then this + //is to be seen as text, else assume it's a mathchar + if (pText->GetText() == pText->GetToken().aText) + HandleText(pText); + else + HandleMath(pText); + } + break; + case SmNodeType::Math: + case SmNodeType::MathIdent: + HandleMath(pNode); + break; + case SmNodeType::SubSup: + HandleSubSupScript(pNode,nLevel); + break; + case SmNodeType::Table: + //Root Node, PILE equivalent, i.e. vertical stack + HandleTable(pNode,nLevel); + break; + case SmNodeType::Matrix: + HandleSmMatrix(static_cast<SmMatrixNode *>(pNode),nLevel); + break; + case SmNodeType::Line: + { + pS->WriteUChar( 0x0a ); + pS->WriteUChar( LINE ); + size_t nSize = pNode->GetNumSubNodes(); + for (size_t i = 0; i < nSize; ++i) + { + if (SmNode *pTemp = pNode->GetSubNode(i)) + HandleNodes(pTemp,nLevel+1); + } + pS->WriteUChar( END ); + break; + } + case SmNodeType::Align: + HandleMAlign(pNode,nLevel); + break; + case SmNodeType::Blank: + pS->WriteUChar( CHAR ); + pS->WriteUChar( 0x98 ); + if (pNode->GetToken().eType == TSBLANK) + pS->WriteUInt16( 0xEB04 ); + else + pS->WriteUInt16( 0xEB05 ); + break; + case SmNodeType::Expression: // same treatment as the default one + default: + { + size_t nSize = pNode->GetNumSubNodes(); + for (size_t i = 0; i < nSize; ++i) + { + if (SmNode *pTemp = pNode->GetSubNode(i)) + HandleNodes(pTemp,nLevel+1); + } + break; + } + } +} + + +int MathType::StartTemplate(sal_uInt16 nSelector,sal_uInt16 nVariation) +{ + int nOldPending=nPendingAttributes; + pS->WriteUChar( TMPL ); //Template + pS->WriteUChar( nSelector ); //selector + pS->WriteUChar( nVariation ); //variation + pS->WriteUChar( 0x00 ); //options + pS->WriteUChar( LINE ); + //there's just no way we can now handle any character + //attributes (from mathtypes perspective) centered + //over an expression but above template attribute + //such as widevec and similar constructs + //we have to drop them + nPendingAttributes=0; + return nOldPending; +} + +void MathType::EndTemplate(int nOldPendingAttributes) +{ + pS->WriteUChar( END ); //end line + pS->WriteUChar( END ); //end template + nPendingAttributes=nOldPendingAttributes; +} + + +void MathType::HandleSmMatrix(SmMatrixNode *pMatrix,int nLevel) +{ + pS->WriteUChar( MATRIX ); + pS->WriteUChar( 0x00 ); //vAlign ? + pS->WriteUChar( 0x00 ); //h_just + pS->WriteUChar( 0x00 ); //v_just + pS->WriteUChar( pMatrix->GetNumRows() ); //v_just + pS->WriteUChar( pMatrix->GetNumCols() ); //v_just + int nBytes=(pMatrix->GetNumRows()+1)*2/8; + if (((pMatrix->GetNumRows()+1)*2)%8) + nBytes++; + for (int j = 0; j < nBytes; j++) + pS->WriteUChar( 0x00 ); //row_parts + nBytes=(pMatrix->GetNumCols()+1)*2/8; + if (((pMatrix->GetNumCols()+1)*2)%8) + nBytes++; + for (int k = 0; k < nBytes; k++) + pS->WriteUChar( 0x00 ); //col_parts + size_t nSize = pMatrix->GetNumSubNodes(); + for (size_t i = 0; i < nSize; ++i) + { + if (SmNode *pTemp = pMatrix->GetSubNode(i)) + { + pS->WriteUChar( LINE ); //line + HandleNodes(pTemp,nLevel+1); + pS->WriteUChar( END ); //end line + } + } + pS->WriteUChar( END ); +} + + +//Root Node, PILE equivalent, i.e. vertical stack +void MathType::HandleTable(SmNode *pNode,int nLevel) +{ + size_t nSize = pNode->GetNumSubNodes(); + //The root of the starmath is a table, if + //we convert this them each iteration of + //conversion from starmath to mathtype will + //add an extra unnecessary level to the + //mathtype output stack which would grow + //without bound in a multi step conversion + + if (nLevel == 0) + pS->WriteUChar( 0x0A ); //initial size + + if ( nLevel || (nSize >1)) + { + pS->WriteUChar( PILE ); + pS->WriteUChar( nHAlign ); //vAlign ? + pS->WriteUChar( 0x01 ); //hAlign + } + + for (size_t i = 0; i < nSize; ++i) + { + if (SmNode *pTemp = pNode->GetSubNode(i)) + { + pS->WriteUChar( LINE ); + HandleNodes(pTemp,nLevel+1); + pS->WriteUChar( END ); + } + } + if (nLevel || (nSize>1)) + pS->WriteUChar( END ); +} + + +void MathType::HandleRoot(SmNode *pNode,int nLevel) +{ + SmNode *pTemp; + pS->WriteUChar( TMPL ); //Template + pS->WriteUChar( 0x0D ); //selector + if (pNode->GetSubNode(0)) + pS->WriteUChar( 0x01 ); //variation + else + pS->WriteUChar( 0x00 ); //variation + pS->WriteUChar( 0x00 ); //options + + if (nullptr != (pTemp = pNode->GetSubNode(2))) + { + pS->WriteUChar( LINE ); //line + HandleNodes(pTemp,nLevel+1); + pS->WriteUChar( END ); + } + + if (nullptr != (pTemp = pNode->GetSubNode(0))) + { + pS->WriteUChar( LINE ); //line + HandleNodes(pTemp,nLevel+1); + pS->WriteUChar( END ); + } + else + pS->WriteUChar( LINE|0x10 ); //dummy line + + + pS->WriteUChar( END ); +} + +sal_uInt8 MathType::HandleCScript(SmNode *pNode,SmNode *pContent,int nLevel, + sal_uInt64 *pPos,bool bTest) +{ + sal_uInt8 nVariation2=0xff; + + if (bTest && pNode->GetSubNode(CSUP+1)) + { + nVariation2=0; + if (pNode->GetSubNode(CSUB+1)) + nVariation2=2; + } + else if (pNode->GetSubNode(CSUB+1)) + nVariation2=1; + + if (nVariation2!=0xff) + { + if (pPos) + *pPos = pS->Tell(); + pS->WriteUChar( TMPL ); //Template + pS->WriteUChar( 0x2B ); //selector + pS->WriteUChar( nVariation2 ); + pS->WriteUChar( 0x00 ); //options + + if (pContent) + { + pS->WriteUChar( LINE ); //line + HandleNodes(pContent,nLevel+1); + pS->WriteUChar( END ); //line + } + else + pS->WriteUChar( LINE|0x10 ); + + pS->WriteUChar( 0x0B ); + + SmNode *pTemp; + if (nullptr != (pTemp = pNode->GetSubNode(CSUB+1))) + { + pS->WriteUChar( LINE ); //line + HandleNodes(pTemp,nLevel+1); + pS->WriteUChar( END ); //line + } + else + pS->WriteUChar( LINE|0x10 ); + if (bTest && nullptr != (pTemp = pNode->GetSubNode(CSUP+1))) + { + pS->WriteUChar( LINE ); //line + HandleNodes(pTemp,nLevel+1); + pS->WriteUChar( END ); //line + } + else + pS->WriteUChar( LINE|0x10 ); + } + return nVariation2; +} + + +/* + Sub and Sup scripts and another problem area, StarMath + can have all possible options used at the same time, whereas + Mathtype cannot. The ordering of the nodes for each system + is quite different as well leading to some complexity + */ +void MathType::HandleSubSupScript(SmNode *pNode,int nLevel) +{ + sal_uInt8 nVariation=0xff; + if (pNode->GetSubNode(LSUP+1)) + { + nVariation=0; + if (pNode->GetSubNode(LSUB+1)) + nVariation=2; + } + else if ( nullptr != pNode->GetSubNode(LSUB+1) ) + nVariation=1; + + SmNode *pTemp; + if (nVariation!=0xff) + { + pS->WriteUChar( TMPL ); //Template + pS->WriteUChar( 0x2c ); //selector + pS->WriteUChar( nVariation ); + pS->WriteUChar( 0x00 ); //options + pS->WriteUChar( 0x0B ); + + if (nullptr != (pTemp = pNode->GetSubNode(LSUB+1))) + { + pS->WriteUChar( LINE ); //line + HandleNodes(pTemp,nLevel+1); + pS->WriteUChar( END ); //line + } + else + pS->WriteUChar( LINE|0x10 ); + if (nullptr != (pTemp = pNode->GetSubNode(LSUP+1))) + { + pS->WriteUChar( LINE ); //line + HandleNodes(pTemp,nLevel+1); + pS->WriteUChar( END ); //line + } + else + pS->WriteUChar( LINE|0x10 ); + pS->WriteUChar( END ); + nVariation=0xff; + } + + + sal_uInt8 nVariation2=HandleCScript(pNode,nullptr,nLevel); + + if (nullptr != (pTemp = pNode->GetSubNode(0))) + { + HandleNodes(pTemp,nLevel+1); + } + + if (nVariation2 != 0xff) + pS->WriteUChar( END ); + + if (nullptr != (pNode->GetSubNode(RSUP+1))) + { + nVariation=0; + if (pNode->GetSubNode(RSUB+1)) + nVariation=2; + } + else if (nullptr != pNode->GetSubNode(RSUB+1)) + nVariation=1; + + if (nVariation!=0xff) + { + pS->WriteUChar( TMPL ); //Template + pS->WriteUChar( 0x0F ); //selector + pS->WriteUChar( nVariation ); + pS->WriteUChar( 0x00 ); //options + pS->WriteUChar( 0x0B ); + + if (nullptr != (pTemp = pNode->GetSubNode(RSUB+1))) + { + pS->WriteUChar( LINE ); //line + HandleNodes(pTemp,nLevel+1); + pS->WriteUChar( END ); //line + } + else + pS->WriteUChar( LINE|0x10 ); + if (nullptr != (pTemp = pNode->GetSubNode(RSUP+1))) + { + pS->WriteUChar( LINE ); //line + HandleNodes(pTemp,nLevel+1); + pS->WriteUChar( END ); //line + } + else + pS->WriteUChar( LINE|0x10 ); + pS->WriteUChar( END ); //line + } + + //After subscript mathtype will keep the size of + //normal text at the subscript size, sigh. + pS->WriteUChar( 0x0A ); +} + + +void MathType::HandleFractions(SmNode *pNode,int nLevel) +{ + SmNode *pTemp; + pS->WriteUChar( TMPL ); //Template + pS->WriteUChar( 0x0E ); //selector + pS->WriteUChar( 0x00 ); //variation + pS->WriteUChar( 0x00 ); //options + + pS->WriteUChar( 0x0A ); + pS->WriteUChar( LINE ); //line + if (nullptr != (pTemp = pNode->GetSubNode(0))) + HandleNodes(pTemp,nLevel+1); + pS->WriteUChar( END ); + + pS->WriteUChar( 0x0A ); + pS->WriteUChar( LINE ); //line + if (nullptr != (pTemp = pNode->GetSubNode(2))) + HandleNodes(pTemp,nLevel+1); + pS->WriteUChar( END ); + + pS->WriteUChar( END ); +} + + +void MathType::HandleBrace(SmNode *pNode,int nLevel) +{ + SmNode *pTemp; + SmNode *pLeft=pNode->GetSubNode(0); + SmNode *pRight=pNode->GetSubNode(2); + + pS->WriteUChar( TMPL ); //Template + bIsReInterpBrace=false; + sal_uInt8 nBSpec=0x10; + auto nLoc = pS->Tell(); + if (pLeft) + { + switch (pLeft->GetToken().eType) + { + case TLANGLE: + pS->WriteUChar( tmANGLE ); //selector + pS->WriteUChar( 0 ); //variation + pS->WriteUChar( 0 ); //options + break; + case TLBRACE: + pS->WriteUChar( tmBRACE ); //selector + pS->WriteUChar( 0 ); //variation + pS->WriteUChar( 0 ); //options + nBSpec+=3; + break; + case TLBRACKET: + pS->WriteUChar( tmBRACK ); //selector + pS->WriteUChar( 0 ); //variation + pS->WriteUChar( 0 ); //options + nBSpec+=3; + break; + case TLFLOOR: + pS->WriteUChar( tmFLOOR ); //selector + pS->WriteUChar( 0 ); //variation + pS->WriteUChar( 0 ); //options + break; + case TLLINE: + pS->WriteUChar( tmBAR ); //selector + pS->WriteUChar( 0 ); //variation + pS->WriteUChar( 0 ); //options + nBSpec+=3; + break; + case TLDLINE: + pS->WriteUChar( tmDBAR ); //selector + pS->WriteUChar( 0 ); //variation + pS->WriteUChar( 0 ); //options + break; + default: + pS->WriteUChar( tmPAREN ); //selector + pS->WriteUChar( 0 ); //variation + pS->WriteUChar( 0 ); //options + nBSpec+=3; + break; + } + } + + if (nullptr != (pTemp = pNode->GetSubNode(1))) + { + pS->WriteUChar( LINE ); //line + HandleNodes(pTemp,nLevel+1); + pS->WriteUChar( END ); //options + } + nSpec=nBSpec; + if (pLeft) + HandleNodes(pLeft,nLevel+1); + if (bIsReInterpBrace) + { + auto nLoc2 = pS->Tell(); + pS->Seek(nLoc); + pS->WriteUChar( 0x2D ); + pS->Seek(nLoc2); + pS->WriteUChar( CHAR ); + pS->WriteUChar( 0x96 ); + pS->WriteUInt16( 0xEC07 ); + bIsReInterpBrace=false; + } + if (pRight) + HandleNodes(pRight,nLevel+1); + nSpec=0x0; + pS->WriteUChar( END ); +} + + +void MathType::HandleVerticalBrace(SmNode *pNode,int nLevel) +{ + SmNode *pTemp; + pS->WriteUChar( TMPL ); //Template + if (pNode->GetToken().eType == TUNDERBRACE) + pS->WriteUChar( tmLHBRACE ); //selector + else + pS->WriteUChar( tmUHBRACE ); //selector + pS->WriteUChar( 0 ); //variation + pS->WriteUChar( 0 ); //options + + if (nullptr != (pTemp = pNode->GetSubNode(0))) + { + pS->WriteUChar( LINE ); //line + HandleNodes(pTemp,nLevel+1); + pS->WriteUChar( END ); //options + } + + if (nullptr != (pTemp = pNode->GetSubNode(2))) + { + pS->WriteUChar( LINE ); //line + HandleNodes(pTemp,nLevel+1); + pS->WriteUChar( END ); //options + } + pS->WriteUChar( END ); +} + +void MathType::HandleOperator(SmNode *pNode,int nLevel) +{ + if (HandleLim(pNode,nLevel)) + return; + + sal_uInt64 nPos; + sal_uInt8 nVariation; + + switch (pNode->GetToken().eType) + { + case TIINT: + case TIIINT: + case TLINT: + case TLLINT: + case TLLLINT: + nVariation=HandleCScript(pNode->GetSubNode(0), + pNode->GetSubNode(1),nLevel,&nPos,false); + break; + default: + nVariation=HandleCScript(pNode->GetSubNode(0), + pNode->GetSubNode(1),nLevel,&nPos); + break; + } + + sal_uInt8 nOldVariation=nVariation; + sal_uInt8 nIntVariation=nVariation; + + sal_uInt64 nPos2=0; + if (nVariation != 0xff) + { + nPos2 = pS->Tell(); + pS->Seek(nPos); + if (nVariation == 2) + { + nIntVariation=0; + nVariation = 1; + } + else if (nVariation == 0) + nVariation = 1; + else if (nVariation == 1) + nVariation = 0; + } + else + { + nVariation = 2; + nIntVariation=0; + } + pS->WriteUChar( TMPL ); + switch(pNode->GetToken().eType) + { + case TINT: + case TINTD: + if (nOldVariation != 0xff) + pS->WriteUChar( 0x18 ); //selector + else + pS->WriteUChar( 0x15 ); //selector + pS->WriteUChar( nIntVariation ); //variation + break; + case TIINT: + if (nOldVariation != 0xff) + { + pS->WriteUChar( 0x19 ); + pS->WriteUChar( 0x01 ); + } + else + { + pS->WriteUChar( 0x16 ); + pS->WriteUChar( 0x00 ); + } + break; + case TIIINT: + if (nOldVariation != 0xff) + { + pS->WriteUChar( 0x1a ); + pS->WriteUChar( 0x01 ); + } + else + { + pS->WriteUChar( 0x17 ); + pS->WriteUChar( 0x00 ); + } + break; + case TLINT: + if (nOldVariation != 0xff) + { + pS->WriteUChar( 0x18 ); + pS->WriteUChar( 0x02 ); + } + else + { + pS->WriteUChar( 0x15 ); + pS->WriteUChar( 0x03 ); + } + break; + case TLLINT: + if (nOldVariation != 0xff) + { + pS->WriteUChar( 0x19 ); + pS->WriteUChar( 0x00 ); + } + else + { + pS->WriteUChar( 0x16 ); + pS->WriteUChar( 0x02 ); + } + break; + case TLLLINT: + if (nOldVariation != 0xff) + { + pS->WriteUChar( 0x1a ); + pS->WriteUChar( 0x00 ); + } + else + { + pS->WriteUChar( 0x17 ); + pS->WriteUChar( 0x02 ); + } + break; + case TSUM: + default: + pS->WriteUChar( 0x1d ); + pS->WriteUChar( nVariation ); + break; + case TPROD: + pS->WriteUChar( 0x1f ); + pS->WriteUChar( nVariation ); + break; + case TCOPROD: + pS->WriteUChar( 0x21 ); + pS->WriteUChar( nVariation ); + break; + } + pS->WriteUChar( 0 ); //options + + if (nPos2) + pS->Seek(nPos2); + else + { + pS->WriteUChar( LINE ); //line + HandleNodes(pNode->GetSubNode(1),nLevel+1); + pS->WriteUChar( END ); //line + pS->WriteUChar( LINE|0x10 ); + pS->WriteUChar( LINE|0x10 ); + } + + pS->WriteUChar( 0x0D ); + switch(pNode->GetToken().eType) + { + case TSUM: + default: + pS->WriteUChar( CHAR ); + pS->WriteUChar( 0x86 ); + pS->WriteUInt16( 0x2211 ); + break; + case TPROD: + pS->WriteUChar( CHAR ); + pS->WriteUChar( 0x86 ); + pS->WriteUInt16( 0x220F ); + break; + case TCOPROD: + pS->WriteUChar( CHAR ); + pS->WriteUChar( 0x8B ); + pS->WriteUInt16( 0x2210 ); + break; + case TIIINT: + case TLLLINT: + pS->WriteUChar( CHAR ); + pS->WriteUChar( 0x86 ); + pS->WriteUInt16( 0x222B ); + [[fallthrough]]; + case TIINT: + case TLLINT: + pS->WriteUChar( CHAR ); + pS->WriteUChar( 0x86 ); + pS->WriteUInt16( 0x222B ); + [[fallthrough]]; + case TINT: + case TINTD: + case TLINT: + pS->WriteUChar( CHAR ); + pS->WriteUChar( 0x86 ); + pS->WriteUInt16( 0x222B ); + break; + } + pS->WriteUChar( END ); + pS->WriteUChar( 0x0A ); +} + + +bool MathType::HandlePile(int &rSetAlign, int nLevel, sal_uInt8 nSelector, sal_uInt8 nVariation) +{ + sal_uInt8 nVAlign; + pS->ReadUChar( nHAlign ); + pS->ReadUChar( nVAlign ); + + HandleAlign(nHAlign, rSetAlign); + + rRet.append(" stack {\n"); + bool bRet = HandleRecords( nLevel+1, nSelector, nVariation, -1, -1 ); + int nRemoveFrom = rRet.getLength() >= 3 ? rRet.getLength() - 3 : 0; + rRet.remove(nRemoveFrom, 2); + rRet.append("} "); + + while (rSetAlign) + { + rRet.append("} "); + rSetAlign--; + } + return bRet; +} + +bool MathType::HandleMatrix(int nLevel, sal_uInt8 nSelector, sal_uInt8 nVariation) +{ + sal_uInt8 nH_just,nV_just,nRows,nCols,nVAlign; + pS->ReadUChar( nVAlign ); + pS->ReadUChar( nH_just ); + pS->ReadUChar( nV_just ); + pS->ReadUChar( nRows ); + pS->ReadUChar( nCols ); + if (!pS->good()) + return false; + int nBytes = ((nRows+1)*2)/8; + if (((nRows+1)*2)%8) + nBytes++; + pS->SeekRel(nBytes); + nBytes = ((nCols+1)*2)/8; + if (((nCols+1)*2)%8) + nBytes++; + pS->SeekRel(nBytes); + rRet.append(" matrix {\n"); + bool bRet = HandleRecords( nLevel+1, nSelector, nVariation, nRows, nCols ); + + sal_Int32 nI = rRet.lastIndexOf('#'); + if (nI > 0) + if (rRet[nI-1] != '#') //missing column + rRet.append("{}"); + + rRet.append("\n} "); + return bRet; +} + +bool MathType::HandleTemplate(int nLevel, sal_uInt8 &rSelector, + sal_uInt8 &rVariation, sal_Int32 &rLastTemplateBracket) +{ + sal_uInt8 nOption; //This appears utterly unused + pS->ReadUChar( rSelector ); + pS->ReadUChar( rVariation ); + pS->ReadUChar( nOption ); + OSL_ENSURE(rSelector < 48,"Selector out of range"); + if ((rSelector >= 21) && (rSelector <=26)) + { + OSL_ENSURE(nOption < 2,"Option out of range"); + } + else if (rSelector <= 12) + { + OSL_ENSURE(nOption < 3,"Option out of range"); + } + + //For the (broken) case where one subscript template ends, and there is + //another one after it, mathtype handles it as if the second one was + //inside the first one and renders it as sub of sub + bool bRemove=false; + if ( (rSelector == 0xf) && (rLastTemplateBracket != -1) ) + { + bRemove=true; + for (sal_Int32 nI = rLastTemplateBracket+1; nI < rRet.getLength(); nI++ ) + if (rRet[nI] != ' ') + { + bRemove=false; + break; + } + } + + //suborderlist + bool bRet = HandleRecords( nLevel+1, rSelector, rVariation ); + + if (bRemove) + { + if (rLastTemplateBracket < rRet.getLength()) + rRet.remove(rLastTemplateBracket, 1); + rRet.append("} "); + rLastTemplateBracket = -1; + } + if (rSelector == 0xf) + rLastTemplateBracket = rRet.lastIndexOf('}'); + else + rLastTemplateBracket = -1; + + rSelector = sal::static_int_cast< sal_uInt8 >(-1); + return bRet; +} + +void MathType::HandleEmblishments() +{ + sal_uInt8 nEmbel; + do + { + pS->ReadUChar( nEmbel ); + if (!pS->good()) + break; + switch (nEmbel) + { + case 0x02: + rRet.append(" dot "); + break; + case 0x03: + rRet.append(" ddot "); + break; + case 0x04: + rRet.append(" dddot "); + break; + case 0x05: + if (!nPostSup) + { + sPost.append(" sup {}"); + nPostSup = sPost.getLength(); + } + sPost.insert(nPostSup-1," ' "); + nPostSup += 3; + break; + case 0x06: + if (!nPostSup) + { + sPost.append(" sup {}"); + nPostSup = sPost.getLength(); + } + sPost.insert(nPostSup-1," '' "); + nPostSup += 4; + break; + case 0x07: + if (!nPostlSup) + { + sPost.append(" lsup {}"); + nPostlSup = sPost.getLength(); + } + sPost.insert(nPostlSup-1," ' "); + nPostlSup += 3; + break; + case 0x08: + rRet.append(" tilde "); + break; + case 0x09: + rRet.append(" hat "); + break; + case 0x0b: + rRet.append(" vec "); + break; + case 0x10: + rRet.append(" overstrike "); + break; + case 0x11: + rRet.append(" bar "); + break; + case 0x12: + if (!nPostSup) + { + sPost.append(" sup {}"); + nPostSup = sPost.getLength(); + } + sPost.insert(nPostSup-1," ''' "); + nPostSup += 5; + break; + case 0x14: + rRet.append(" breve "); + break; + default: + OSL_ENSURE(nEmbel < 21,"Embel out of range"); + break; + } + if (nVersion < 3) + break; + }while (nEmbel); +} + +void MathType::HandleSetSize() +{ + sal_uInt8 nTemp(0); + pS->ReadUChar(nTemp); + switch (nTemp) + { + case 101: + pS->ReadInt16( nLSize ); + nLSize = -nLSize; + break; + case 100: + pS->ReadUChar( nTemp ); + nLSize = nTemp; + pS->ReadInt16( nDSize ); + break; + default: + nLSize = nTemp; + pS->ReadUChar( nTemp ); + nDSize = nTemp-128; + break; + } +} + +bool MathType::HandleChar(sal_Int32 &rTextStart, int &rSetSize, int nLevel, + sal_uInt8 nTag, sal_uInt8 nSelector, sal_uInt8 nVariation, bool bSilent) +{ + sal_Unicode nChar(0); + bool bRet = true; + + if (xfAUTO(nTag)) + { + //This is a candidate for function recognition, whatever + //that is! + } + + sal_uInt8 nOldTypeFace = nTypeFace; + pS->ReadUChar( nTypeFace ); + if (nVersion < 3) + { + sal_uInt8 nChar8(0); + pS->ReadUChar( nChar8 ); + nChar = nChar8; + } + else + pS->ReadUtf16( nChar ); + + /* + bad character, old mathtype < 3 has these + */ + if (nChar < 0x20) + return bRet; + + if (xfEMBELL(nTag)) + { + //A bit tricky, the character emblishments for + //mathtype can all be listed after each other, in + //starmath some must go before the character and some + //must go after. In addition some of the emblishments + //may repeated and in starmath some of these groups + //must be gathered together. sPost is the portion that + //follows the char and nPostSup and nPostlSup are the + //indexes at which this class of emblishment is + //collated together + sPost = ""; + nPostSup = nPostlSup = 0; + int nOriglen=rRet.getLength()-rTextStart; + rRet.append(" {"); // #i24340# make what would be "vec {A}_n" become "{vec {A}}_n" + if ((!bSilent) && (nOriglen > 1)) + rRet.append("\""); + bRet = HandleRecords( nLevel+1, nSelector, nVariation ); + if (!bSilent) + { + if (nOriglen > 1) + { + OUString aStr; + TypeFaceToString(aStr,nOldTypeFace); + rRet.insert(std::min(rTextStart, rRet.getLength()), aStr + "\""); + + aStr.clear(); + TypeFaceToString(aStr,nTypeFace); + rRet.append(aStr + "{"); + } + else + rRet.append(" {"); + rTextStart = rRet.getLength(); + } + } + + if (!bSilent) + { + sal_Int32 nOldLen = rRet.getLength(); + if ( + HandleSize(nLSize,nDSize,rSetSize) || + (nOldTypeFace != nTypeFace) + ) + { + if ((nOldLen - rTextStart) > 1) + { + rRet.insert(nOldLen, "\""); + OUString aStr; + TypeFaceToString(aStr,nOldTypeFace); + rRet.insert(rTextStart, aStr + "\""); + } + rTextStart = rRet.getLength(); + } + nOldLen = rRet.getLength(); + if (!LookupChar(nChar,rRet,nVersion,nTypeFace)) + { + if (nOldLen - rTextStart > 1) + { + rRet.insert(nOldLen, "\""); + OUString aStr; + TypeFaceToString(aStr,nOldTypeFace); + rRet.insert(rTextStart, aStr + "\""); + } + rTextStart = rRet.getLength(); + } + lcl_PrependDummyTerm(rRet, rTextStart); + } + + if ((xfEMBELL(nTag)) && (!bSilent)) + { + rRet.append("}}" + sPost); // #i24340# make what would be "vec {A}_n" become "{vec {A}}_n" + rTextStart = rRet.getLength(); + } + return bRet; +} + +bool MathType::HandleLim(SmNode *pNode,int nLevel) +{ + bool bRet=false; + //Special case for the "lim" option in StarMath + if ((pNode->GetToken().eType == TLIM) + || (pNode->GetToken().eType == TLIMSUP) + || (pNode->GetToken().eType == TLIMINF) + ) + { + if (pNode->GetSubNode(1)) + { + sal_uInt8 nVariation2=HandleCScript(pNode->GetSubNode(0),nullptr, + nLevel); + + pS->WriteUChar( 0x0A ); + pS->WriteUChar( LINE ); //line + pS->WriteUChar( CHAR|0x10 ); + pS->WriteUChar( 0x82 ); + pS->WriteUInt16( 'l' ); + pS->WriteUChar( CHAR|0x10 ); + pS->WriteUChar( 0x82 ); + pS->WriteUInt16( 'i' ); + pS->WriteUChar( CHAR|0x10 ); + pS->WriteUChar( 0x82 ); + pS->WriteUInt16( 'm' ); + + if (pNode->GetToken().eType == TLIMSUP) + { + pS->WriteUChar( CHAR ); //some space + pS->WriteUChar( 0x98 ); + pS->WriteUInt16( 0xEB04 ); + + pS->WriteUChar( CHAR|0x10 ); + pS->WriteUChar( 0x82 ); + pS->WriteUInt16( 's' ); + pS->WriteUChar( CHAR|0x10 ); + pS->WriteUChar( 0x82 ); + pS->WriteUInt16( 'u' ); + pS->WriteUChar( CHAR|0x10 ); + pS->WriteUChar( 0x82 ); + pS->WriteUInt16( 'p' ); + } + else if (pNode->GetToken().eType == TLIMINF) + { + pS->WriteUChar( CHAR ); //some space + pS->WriteUChar( 0x98 ); + pS->WriteUInt16( 0xEB04 ); + + pS->WriteUChar( CHAR|0x10 ); + pS->WriteUChar( 0x82 ); + pS->WriteUInt16( 'i' ); + pS->WriteUChar( CHAR|0x10 ); + pS->WriteUChar( 0x82 ); + pS->WriteUInt16( 'n' ); + pS->WriteUChar( CHAR|0x10 ); + pS->WriteUChar( 0x82 ); + pS->WriteUInt16( 'f' ); + } + + + pS->WriteUChar( CHAR ); //some space + pS->WriteUChar( 0x98 ); + pS->WriteUInt16( 0xEB04 ); + + if (nVariation2 != 0xff) + { + pS->WriteUChar( END ); + pS->WriteUChar( END ); + } + HandleNodes(pNode->GetSubNode(1),nLevel+1); + bRet = true; + } + } + return bRet; +} + +void MathType::HandleMAlign(SmNode *pNode,int nLevel) +{ + sal_uInt8 nPushedHAlign=nHAlign; + switch(pNode->GetToken().eType) + { + case TALIGNC: + nHAlign=2; + break; + case TALIGNR: + nHAlign=3; + break; + default: + nHAlign=1; + break; + } + size_t nSize = pNode->GetNumSubNodes(); + for (size_t i = 0; i < nSize; ++i) + { + if (SmNode *pTemp = pNode->GetSubNode(i)) + HandleNodes(pTemp,nLevel+1); + } + nHAlign=nPushedHAlign; +} + +void MathType::HandleMath(SmNode *pNode) +{ + if (pNode->GetToken().eType == TMLINE) + { + pS->WriteUChar( END ); + pS->WriteUChar( LINE ); + bIsReInterpBrace=true; + return; + } + SmMathSymbolNode *pTemp = static_cast<SmMathSymbolNode *>(pNode); + for(sal_Int32 i=0;i<pTemp->GetText().getLength();i++) + { + sal_Unicode nArse = SmTextNode::ConvertSymbolToUnicode(pTemp->GetText()[i]); + if ((nArse == 0x2224) || (nArse == 0x2288) || (nArse == 0x2285) || + (nArse == 0x2289)) + { + pS->WriteUChar( CHAR|0x20 ); + } + else if (nPendingAttributes && + (i == ((pTemp->GetText().getLength()+1)/2)-1)) + { + pS->WriteUChar( 0x22 ); + } + else + pS->WriteUChar( CHAR ); //char without formula recognition + //The typeface seems to be MTEXTRA for unicode characters, + //though how to determine when mathtype chooses one over + //the other is unknown. This should do the trick + //nevertheless. + sal_uInt8 nBias; + if ( (nArse == 0x2213) || (nArse == 0x2218) || + (nArse == 0x210F) || ( + (nArse >= 0x22EE) && (nArse <= 0x22FF) + )) + { + nBias = 0xB; //typeface + } + else if ((nArse == 0x2F) || (nArse == 0x2225)) + nBias = 0x2; //typeface + else if ((nArse > 0x2000) || (nArse == 0x00D7)) + nBias = 0x6; //typeface + else if (nArse == 0x3d1) + nBias = 0x4; + else if ((nArse > 0xFF) && ((nArse < 0x393) || (nArse > 0x3c9))) + nBias = 0xB; //typeface + else + nBias = 0x3; //typeface + + pS->WriteUChar( nSpec+nBias+128 ); //typeface + + if (nArse == 0x2224) + { + pS->WriteUInt16( 0x7C ); + pS->WriteUChar( EMBEL ); + pS->WriteUChar( 0x0A ); + pS->WriteUChar( END ); //end embel + pS->WriteUChar( END ); //end embel + } + else if (nArse == 0x2225) + pS->WriteUInt16( 0xEC09 ); + else if (nArse == 0xE421) + pS->WriteUInt16( 0x2265 ); + else if (nArse == 0x230A) + pS->WriteUInt16( 0xF8F0 ); + else if (nArse == 0x230B) + pS->WriteUInt16( 0xF8FB ); + else if (nArse == 0xE425) + pS->WriteUInt16( 0x2264 ); + else if (nArse == 0x226A) + { + pS->WriteUInt16( 0x3C ); + pS->WriteUChar( CHAR ); + pS->WriteUChar( 0x98 ); + pS->WriteUInt16( 0xEB01 ); + pS->WriteUChar( CHAR ); + pS->WriteUChar( 0x86 ); + pS->WriteUInt16( 0x3c ); + } + else if (nArse == 0x2288) + { + pS->WriteUInt16( 0x2286 ); + pS->WriteUChar( EMBEL ); + pS->WriteUChar( 0x0A ); + pS->WriteUChar( END ); //end embel + pS->WriteUChar( END ); //end embel + } + else if (nArse == 0x2289) + { + pS->WriteUInt16( 0x2287 ); + pS->WriteUChar( EMBEL ); + pS->WriteUChar( 0x0A ); + pS->WriteUChar( END ); //end embel + pS->WriteUChar( END ); //end embel + } + else if (nArse == 0x2285) + { + pS->WriteUInt16( 0x2283 ); + pS->WriteUChar( EMBEL ); + pS->WriteUChar( 0x0A ); + pS->WriteUChar( END ); //end embel + pS->WriteUChar( END ); //end embel + } + else + pS->WriteUInt16( nArse ); + } + nPendingAttributes = 0; +} + +void MathType::HandleAttributes(SmNode *pNode,int nLevel) +{ + int nOldPending = 0; + SmNode *pTemp = nullptr; + SmTextNode *pIsText = nullptr; + + if (nullptr != (pTemp = pNode->GetSubNode(0))) + { + pIsText = static_cast<SmTextNode *>(pNode->GetSubNode(1)); + + switch (pTemp->GetToken().eType) + { + case TWIDEVEC: + //there's just no way we can now handle any character + //attributes (from mathtypes perspective) centered + //over an expression but above template attributes + //such as widevec and similar constructs + //we have to drop them + nOldPending = StartTemplate(0x2f,0x01); + break; + case TCHECK: //Not Exportable + case TACUTE: //Not Exportable + case TGRAVE: //Not Exportable + case TCIRCLE: //Not Exportable + case TWIDEHARPOON: //Not Exportable + case TWIDETILDE: //Not Exportable + case TWIDEHAT: //Not Exportable + break; + case TUNDERLINE: + nOldPending = StartTemplate(0x10); + break; + case TOVERLINE: //If the next node is not text + //or text with more than one char + if (!pIsText || + pIsText->GetToken().eType != TTEXT || + pIsText->GetText().getLength() > 1) + nOldPending = StartTemplate(0x11); + break; + default: + nPendingAttributes++; + break; + } + } + + if (pIsText) + HandleNodes(pIsText,nLevel+1); + + if (pTemp) + { + switch (pTemp->GetToken().eType) + { + case TWIDEVEC: + case TUNDERLINE: + EndTemplate(nOldPending); + break; + case TOVERLINE: + if (!pIsText || + pIsText->GetToken().eType != TTEXT || + pIsText->GetText().getLength() > 1) + EndTemplate(nOldPending); + break; + default: + break; + } + } + + //if there was no suitable place to put the attribute, + //then we have to just give up on it + if (nPendingAttributes) + nPendingAttributes--; + else + { + if ((nInsertion != 0) && nullptr != (pTemp = pNode->GetSubNode(0))) + { + auto nPos = pS->Tell(); + nInsertion--; + pS->Seek(nInsertion); + switch(pTemp->GetToken().eType) + { + case TACUTE: //Not Exportable + case TGRAVE: //Not Exportable + case TCIRCLE: //Not Exportable + break; + case TCDOT: + pS->WriteUChar( 2 ); + break; + case TDDOT: + pS->WriteUChar( 3 ); + break; + case TDDDOT: + pS->WriteUChar( 4 ); + break; + case TTILDE: + pS->WriteUChar( 8 ); + break; + case THAT: + pS->WriteUChar( 9 ); + break; + case TVEC: + pS->WriteUChar( 11 ); + break; + case TOVERSTRIKE: + pS->WriteUChar( 16 ); + break; + case TOVERLINE: + if (pIsText && + (pIsText->GetToken().eType == TTEXT && + pIsText->GetText().getLength() == 1)) + pS->WriteUChar( 17 ); + break; + case TBREVE: + pS->WriteUChar( 20 ); + break; + case TWIDEVEC: + case TWIDEHARPOON: + case TUNDERLINE: + case TWIDETILDE: + case TWIDEHAT: + break; + case TBAR: + pS->WriteUChar( 17 ); + break; + default: + pS->WriteUChar( 2 ); + break; + } + pS->Seek(nPos); + } + } +} + +void MathType::HandleText(SmNode *pNode) +{ + SmTextNode *pTemp = static_cast<SmTextNode *>(pNode); + for(sal_Int32 i=0;i<pTemp->GetText().getLength();i++) + { + if (nPendingAttributes && + (i == ((pTemp->GetText().getLength()+1)/2)-1)) + { + pS->WriteUChar( 0x22 ); //char, with attributes right + //after the character + } + else + pS->WriteUChar( CHAR ); + + sal_uInt8 nFace = 0x1; + if (pNode->GetFont().GetItalic() == ITALIC_NORMAL) + nFace = 0x3; + else if (pNode->GetFont().GetWeight() == WEIGHT_BOLD) + nFace = 0x7; + pS->WriteUChar( nFace+128 ); //typeface + sal_uInt16 nChar = pTemp->GetText()[i]; + pS->WriteUInt16( SmTextNode::ConvertSymbolToUnicode(nChar) ); + + //Mathtype can only have these sort of character + //attributes on a single character, starmath can put them + //anywhere, when the entity involved is a text run this is + //a large effort to place the character attribute on the + //central mathtype character so that it does pretty much + //what the user probably has in mind. The attributes + //filled in here are dummy ones which are replaced in the + //ATTRIBUTE handler if a suitable location for the + //attributes was found here. Unfortunately it is + //possible for starmath to place character attributes on + //entities which cannot occur in mathtype e.g. a Summation + //symbol so these attributes may be lost + if (nPendingAttributes && + (i == ((pTemp->GetText().getLength()+1)/2)-1)) + { + pS->WriteUChar( EMBEL ); + while (nPendingAttributes) + { + pS->WriteUChar( 2 ); + //wedge the attributes in here and clear + //the pending stack + nPendingAttributes--; + } + nInsertion=pS->Tell(); + pS->WriteUChar( END ); //end embel + pS->WriteUChar( END ); //end embel + } + } +} + +extern "C" SAL_DLLPUBLIC_EXPORT bool TestImportMathType(SvStream &rStream) +{ + OUStringBuffer sText; + MathType aEquation(sText); + bool bRet = false; + try + { + bRet = aEquation.Parse(&rStream); + } + catch (const std::out_of_range&) + { + } + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/mathtype.hxx b/starmath/source/mathtype.hxx new file mode 100644 index 0000000000..cd06cfcddf --- /dev/null +++ b/starmath/source/mathtype.hxx @@ -0,0 +1,184 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <node.hxx> + +#include <o3tl/sorted_vector.hxx> + +class SfxMedium; +class SotStorage; +class SvStream; + +class MathTypeFont +{ +public: + sal_uInt8 nTface; + sal_uInt8 nStyle; + MathTypeFont() : nTface(0),nStyle(0) {} + explicit MathTypeFont(sal_uInt8 nFace) : nTface(nFace),nStyle(0) {} + void AppendStyleToText(OUString &rS); +}; + +struct LessMathTypeFont +{ + bool operator() (const MathTypeFont &rValue1, + const MathTypeFont &rValue2) const + { + return rValue1.nTface < rValue2.nTface; + } +}; + +typedef o3tl::sorted_vector< MathTypeFont, LessMathTypeFont > MathTypeFontSet; + +class MathType +{ +public: + explicit MathType(OUStringBuffer &rIn) + : nVersion(0) + , pS(nullptr) + , rRet(rIn) + , pTree(nullptr) + , nHAlign(0) + , nPendingAttributes(0) + , nInsertion(0) + , nLSize(0) + , nDSize(0) + , nCurSize(0) + , nLastSize(0) + , nSpec(0) + , bIsReInterpBrace(false) + , nPostSup(0) + , nPostlSup(0) + , nTypeFace(0) + { + Init(); + } + + MathType(OUStringBuffer &rIn,SmNode *pIn) + : nVersion(0) + , pS(nullptr) + , rRet(rIn) + , pTree(pIn) + , nHAlign(2) + , nPendingAttributes(0) + , nInsertion(0) + , nLSize(0) + , nDSize(0) + , nCurSize(0) + , nLastSize(0) + , nSpec(0) + , bIsReInterpBrace(false) + , nPostSup(0) + , nPostlSup(0) + , nTypeFace(0) + { + Init(); + } + + bool Parse(SotStorage* pStor); + bool Parse(SvStream* pStream); + bool ConvertFromStarMath( SfxMedium& rMedium ); + +private: +/*Ver 2 Header*/ + sal_uInt8 nVersion; + + SvStream* pS; + + void Init(); + + bool HandleRecords(int nLevel, sal_uInt8 nSelector =0xFF, + sal_uInt8 nVariation =0xFF, int nRows =0, int nCols =0); + bool HandleSize(sal_Int16 nLSize, sal_Int16 nDSize, int &rSetSize); + void HandleAlign(sal_uInt8 nHAlign, int &rSetAlign); + bool HandlePile(int &rSetAlign, int nLevel, sal_uInt8 nSelector, sal_uInt8 nVariation); + bool HandleMatrix(int nLevel, sal_uInt8 nSelector, sal_uInt8 nVariarion); + void HandleMatrixSeparator(int nMatrixRows, int nMatrixCols, int &rCurCol, int &rCurRow); + bool HandleTemplate(int nLevel, sal_uInt8 &rSelector, sal_uInt8 &rVariation, + sal_Int32 &rLastTemplateBracket); + void HandleEmblishments(); + void HandleSetSize(); + bool HandleChar(sal_Int32 &rTextStart, int &rSetSize, int nLevel, + sal_uInt8 nTag, sal_uInt8 nSelector, sal_uInt8 nVariation, bool bSilent); + void HandleNudge(); + + static int xfLMOVE(sal_uInt8 nTest) {return nTest&0x80;} + static int xfAUTO(sal_uInt8 nTest) {return nTest&0x10;} + static int xfEMBELL(sal_uInt8 nTest) {return nTest&0x20;} + static int xfNULL(sal_uInt8 nTest) {return nTest&0x10;} + + void HandleNodes(SmNode *pNode,int nLevel); + int StartTemplate(sal_uInt16 nSelector,sal_uInt16 nVariation=0); + void EndTemplate(int nOldPendingAttributes); + void HandleSmMatrix(SmMatrixNode *pMatrix,int nLevel); + void HandleTable(SmNode *pNode,int nLevel); + void HandleRoot(SmNode *pNode,int nLevel); + void HandleSubSupScript(SmNode *pNode,int nLevel); + sal_uInt8 HandleCScript(SmNode *pNode,SmNode *pContent,int nLevel, + sal_uInt64 *pPos=nullptr,bool bTest=true); + void HandleFractions(SmNode *pNode,int nLevel); + void HandleBrace(SmNode *pNode,int nLevel); + void HandleVerticalBrace(SmNode *pNode,int nLevel); + void HandleOperator(SmNode *pNode,int nLevel); + bool HandleLim(SmNode *pNode,int nLevel); + void HandleMAlign(SmNode *pNode,int nLevel); + void HandleMath(SmNode *pNode); + void HandleText(SmNode *pNode); + void HandleAttributes(SmNode *pNode,int nLevel); + void TypeFaceToString(OUString &rRet,sal_uInt8 nFace); + + OUStringBuffer &rRet; + SmNode *pTree; + + sal_uInt8 nHAlign; + + int nPendingAttributes; + sal_uInt64 nInsertion; + + sal_Int16 nLSize; + sal_Int16 nDSize; + sal_Int16 nCurSize; + sal_Int16 nLastSize; + sal_uInt8 nSpec; + bool bIsReInterpBrace; + OUStringBuffer sPost; + sal_Int32 nPostSup; + sal_Int32 nPostlSup; + sal_uInt8 nTypeFace; + MathTypeFontSet aUserStyles; + + enum MTOKENS {END,LINE,CHAR,TMPL,PILE,MATRIX,EMBEL,RULER,FONT,SIZE}; + enum MTEMPLATES + { + tmANGLE,tmPAREN,tmBRACE,tmBRACK,tmBAR,tmDBAR,tmFLOOR,tmCEILING, + tmLBLB,tmRBRB,tmRBLB,tmLBRP,tmLPRB,tmROOT,tmFRACT,tmSCRIPT,tmUBAR, + tmOBAR,tmLARROW,tmRARROW,tmBARROW,tmSINT,tmDINT,tmTINT,tmSSINT, + tmDSINT,tmTSINT,tmUHBRACE,tmLHBRACE,tmSUM,tmISUM,tmPROD,tmIPROD, + tmCOPROD,tmICOPROD,tmUNION,tmIUNION,tmINTER,tmIINTER,tmLIM,tmLDIV, + tmSLFRACT,tmINTOP,tmSUMOP,tmLSCRIPT,tmDIRAC,tmUARROW,tmOARROW, + tmOARC + }; +public: + static bool LookupChar(sal_Unicode nChar,OUStringBuffer &rRet, + sal_uInt8 nVersion,sal_uInt8 nTypeFace=0); +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/node.cxx b/starmath/source/node.cxx new file mode 100644 index 0000000000..778baaa74d --- /dev/null +++ b/starmath/source/node.cxx @@ -0,0 +1,2491 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include <symbol.hxx> +#include <smmod.hxx> +#include "tmpdevice.hxx" +#include <utility> +#include <visitors.hxx> +#include <tools/UnitConversion.hxx> +#include <vcl/metric.hxx> +#include <o3tl/safeint.hxx> +#include <osl/diagnose.h> +#include <basegfx/numeric/ftools.hxx> +#include <unicode/uchar.h> +#include <unicode/uscript.h> + +namespace { + +template<typename F> +void ForEachNonNull(SmNode *pNode, F && f) +{ + size_t nSize = pNode->GetNumSubNodes(); + for (size_t i = 0; i < nSize; ++i) + { + SmNode *pSubNode = pNode->GetSubNode(i); + if (pSubNode != nullptr) + f(pSubNode); + } +} + +} + +SmNode::SmNode(SmNodeType eNodeType, SmToken aNodeToken) + : maNodeToken(std::move( aNodeToken )) + , meType( eNodeType ) + , meScaleMode( SmScaleMode::None ) + , meRectHorAlign( RectHorAlign::Left ) + , mnFlags( FontChangeMask::None ) + , mnAttributes( FontAttribute::None ) + , mbIsPhantom( false ) + , mbIsSelected( false ) + , mnAccIndex( -1 ) + , mpParentNode( nullptr ) +{ +} + +SmNode::~SmNode() +{ +} + +const SmNode * SmNode::GetLeftMost() const + // returns leftmost node of current subtree. + //! (this assumes the one with index 0 is always the leftmost subnode + //! for the current node). +{ + const SmNode *pNode = GetNumSubNodes() > 0 ? + GetSubNode(0) : nullptr; + + return pNode ? pNode->GetLeftMost() : this; +} + + +void SmNode::SetPhantom(bool bIsPhantomP) +{ + if (! (Flags() & FontChangeMask::Phantom)) + mbIsPhantom = bIsPhantomP; + + bool b = mbIsPhantom; + ForEachNonNull(this, [b](SmNode *pNode){pNode->SetPhantom(b);}); +} + + +void SmNode::SetColor(const Color& rColor) +{ + if (! (Flags() & FontChangeMask::Color)) + GetFont().SetColor(rColor); + + ForEachNonNull(this, [&rColor](SmNode *pNode){pNode->SetColor(rColor);}); +} + + +void SmNode::SetAttribute(FontAttribute nAttrib) +{ + if ( + (nAttrib == FontAttribute::Bold && !(Flags() & FontChangeMask::Bold)) || + (nAttrib == FontAttribute::Italic && !(Flags() & FontChangeMask::Italic)) + ) + { + mnAttributes |= nAttrib; + } + + ForEachNonNull(this, [nAttrib](SmNode *pNode){pNode->SetAttribute(nAttrib);}); +} + + +void SmNode::ClearAttribute(FontAttribute nAttrib) +{ + if ( + (nAttrib == FontAttribute::Bold && !(Flags() & FontChangeMask::Bold)) || + (nAttrib == FontAttribute::Italic && !(Flags() & FontChangeMask::Italic)) + ) + { + mnAttributes &= ~nAttrib; + } + + ForEachNonNull(this, [nAttrib](SmNode *pNode){pNode->ClearAttribute(nAttrib);}); +} + + +void SmNode::SetFont(const SmFace &rFace) +{ + if (!(Flags() & FontChangeMask::Face)) + GetFont() = rFace; + ForEachNonNull(this, [&rFace](SmNode *pNode){pNode->SetFont(rFace);}); +} + + +void SmNode::SetFontSize(const Fraction &rSize, FontSizeType nType) + //! 'rSize' is in units of pts +{ + Size aFntSize; + + if (!(Flags() & FontChangeMask::Size)) + { + Fraction aVal(conversionFract(o3tl::Length::pt, SmO3tlLengthUnit()) * rSize); + tools::Long nHeight = static_cast<tools::Long>(aVal); + + aFntSize = GetFont().GetFontSize(); + aFntSize.setWidth( 0 ); + switch(nType) + { + case FontSizeType::ABSOLUT: + aFntSize.setHeight( nHeight ); + break; + + case FontSizeType::PLUS: + aFntSize.AdjustHeight(nHeight ); + break; + + case FontSizeType::MINUS: + aFntSize.AdjustHeight( -nHeight ); + break; + + case FontSizeType::MULTIPLY: + aFntSize.setHeight( static_cast<tools::Long>(Fraction(aFntSize.Height()) * rSize) ); + break; + + case FontSizeType::DIVIDE: + if (rSize != Fraction(0)) + aFntSize.setHeight( static_cast<tools::Long>(Fraction(aFntSize.Height()) / rSize) ); + break; + default: + break; + } + + // check the requested size against maximum value + const int nMaxVal = o3tl::convert(128, o3tl::Length::pt, SmO3tlLengthUnit()); + if (aFntSize.Height() > nMaxVal) + aFntSize.setHeight( nMaxVal ); + + GetFont().SetSize(aFntSize); + } + + ForEachNonNull(this, [&rSize, &nType](SmNode *pNode){pNode->SetFontSize(rSize, nType);}); +} + + +void SmNode::SetSize(const Fraction &rSize) +{ + GetFont() *= rSize; + + ForEachNonNull(this, [&rSize](SmNode *pNode){pNode->SetSize(rSize);}); +} + + +void SmNode::SetRectHorAlign(RectHorAlign eHorAlign, bool bApplyToSubTree ) +{ + meRectHorAlign = eHorAlign; + + if (bApplyToSubTree) + ForEachNonNull(this, [eHorAlign](SmNode *pNode){pNode->SetRectHorAlign(eHorAlign);}); +} + + +void SmNode::PrepareAttributes() +{ + GetFont().SetWeight((Attributes() & FontAttribute::Bold) ? WEIGHT_BOLD : WEIGHT_NORMAL); + GetFont().SetItalic((Attributes() & FontAttribute::Italic) ? ITALIC_NORMAL : ITALIC_NONE); +} + + +void SmNode::Prepare(const SmFormat &rFormat, const SmDocShell &rDocShell, int nDepth) +{ + if (nDepth > 1024) + throw std::range_error("parser depth limit"); + + mbIsPhantom = false; + mnFlags = FontChangeMask::None; + mnAttributes = FontAttribute::None; + + switch (rFormat.GetHorAlign()) + { case SmHorAlign::Left: meRectHorAlign = RectHorAlign::Left; break; + case SmHorAlign::Center: meRectHorAlign = RectHorAlign::Center; break; + case SmHorAlign::Right: meRectHorAlign = RectHorAlign::Right; break; + } + + GetFont() = rFormat.GetFont(FNT_MATH); + OSL_ENSURE( GetFont().GetCharSet() == RTL_TEXTENCODING_UNICODE, + "unexpected CharSet" ); + GetFont().SetWeight(WEIGHT_NORMAL); + GetFont().SetItalic(ITALIC_NONE); + + ForEachNonNull(this, [&rFormat, &rDocShell, nDepth](SmNode *pNode){pNode->Prepare(rFormat, rDocShell, nDepth + 1);}); +} + +void SmNode::Move(const Point& rVector) +{ + if (rVector.X() == 0 && rVector.Y() == 0) + return; + + SmRect::Move(rVector); + + ForEachNonNull(this, [&rVector](SmNode *pNode){pNode->Move(rVector);}); +} + +void SmNode::AdaptToX(OutputDevice &/*rDev*/, sal_uLong /*nWidth*/) +{ +} + + +void SmNode::AdaptToY(OutputDevice &/*rDev*/, sal_uLong /*nHeight*/) +{ +} + + +const SmNode * SmNode::FindTokenAt(sal_uInt16 nRow, sal_uInt16 nCol) const + // returns (first) ** visible ** (sub)node with the tokens text at + // position 'nRow', 'nCol'. + //! (there should be exactly one such node if any) +{ + if ( IsVisible() + && nRow == GetSelection().nStartPara + && nCol >= GetSelection().nStartPos && nCol <= GetSelection().nEndPos ) + return this; + else + { + size_t nNumSubNodes = GetNumSubNodes(); + for (size_t i = 0; i < nNumSubNodes; ++i) + { + const SmNode *pNode = GetSubNode(i); + + if (!pNode) + continue; + + const SmNode *pResult = pNode->FindTokenAt(nRow, nCol); + if (pResult) + return pResult; + } + } + + return nullptr; +} + + +const SmNode * SmNode::FindRectClosestTo(const Point &rPoint) const +{ + tools::Long nDist = LONG_MAX; + const SmNode *pResult = nullptr; + + if (IsVisible()) + pResult = this; + else + { + size_t nNumSubNodes = GetNumSubNodes(); + for (size_t i = 0; i < nNumSubNodes; ++i) + { + const SmNode *pNode = GetSubNode(i); + + if (!pNode) + continue; + + const SmNode *pFound = pNode->FindRectClosestTo(rPoint); + if (pFound) + { + tools::Long nTmp = pFound->OrientedDist(rPoint); + if (nTmp < nDist) + { + nDist = nTmp; + pResult = pFound; + + // quit immediately if 'rPoint' is inside the *should not + // overlap with other rectangles* part. + // This (partly) serves for getting the attributes in eg + // "bar overstrike a". + // ('nDist < 0' is used as *quick shot* to avoid evaluation of + // the following expression, where the result is already determined) + if (nDist < 0 && pFound->IsInsideRect(rPoint)) + break; + } + } + } + } + + return pResult; +} + +const SmNode * SmNode::FindNodeWithAccessibleIndex(sal_Int32 nAccIdx) const +{ + const SmNode *pResult = nullptr; + + sal_Int32 nIdx = GetAccessibleIndex(); + OUStringBuffer aTxt; + if (nIdx >= 0) + GetAccessibleText( aTxt ); // get text if used in following 'if' statement + + if (nIdx >= 0 + && nIdx <= nAccIdx && nAccIdx < nIdx + aTxt.getLength()) + pResult = this; + else + { + size_t nNumSubNodes = GetNumSubNodes(); + for (size_t i = 0; i < nNumSubNodes; ++i) + { + const SmNode *pNode = GetSubNode(i); + if (!pNode) + continue; + + pResult = pNode->FindNodeWithAccessibleIndex(nAccIdx); + if (pResult) + return pResult; + } + } + + return pResult; +} + + +SmStructureNode::~SmStructureNode() +{ + ForEachNonNull(this, std::default_delete<SmNode>()); +} + + +void SmStructureNode::ClearSubNodes() +{ + maSubNodes.clear(); +} + +void SmStructureNode::SetSubNodes(std::unique_ptr<SmNode> pFirst, std::unique_ptr<SmNode> pSecond, std::unique_ptr<SmNode> pThird) +{ + size_t nSize = pThird ? 3 : (pSecond ? 2 : (pFirst ? 1 : 0)); + maSubNodes.resize( nSize ); + if (pFirst) + maSubNodes[0] = pFirst.release(); + if (pSecond) + maSubNodes[1] = pSecond.release(); + if (pThird) + maSubNodes[2] = pThird.release(); + + ClaimPaternity(); +} + +void SmStructureNode::SetSubNodes(SmNode* pFirst, SmNode* pSecond, SmNode* pThird) +{ + size_t nSize = pThird ? 3 : (pSecond ? 2 : (pFirst ? 1 : 0)); + maSubNodes.resize( nSize ); + if (pFirst) + maSubNodes[0] = pFirst; + if (pSecond) + maSubNodes[1] = pSecond; + if (pThird) + maSubNodes[2] = pThird; + + ClaimPaternity(); +} + +void SmStructureNode::SetSubNodesBinMo(std::unique_ptr<SmNode> pFirst, std::unique_ptr<SmNode> pSecond, std::unique_ptr<SmNode> pThird) +{ + if(GetType()==SmNodeType::BinDiagonal) + { + size_t nSize = pSecond ? 3 : (pThird ? 2 : (pFirst ? 1 : 0)); + maSubNodes.resize( nSize ); + if (pFirst) + maSubNodes[0] = pFirst.release(); + if (pSecond) + maSubNodes[2] = pSecond.release(); + if (pThird) + maSubNodes[1] = pThird.release(); + } + else + { + size_t nSize = pThird ? 3 : (pSecond ? 2 : (pFirst ? 1 : 0)); + maSubNodes.resize( nSize ); + if (pFirst) + maSubNodes[0] = pFirst.release(); + if (pSecond) + maSubNodes[1] = pSecond.release(); + if (pThird) + maSubNodes[2] = pThird.release(); + } + ClaimPaternity(); +} + +void SmStructureNode::SetSubNodes(SmNodeArray&& rNodeArray) +{ + maSubNodes = std::move(rNodeArray); + ClaimPaternity(); +} + +bool SmStructureNode::IsVisible() const +{ + return false; +} + +size_t SmStructureNode::GetNumSubNodes() const +{ + return maSubNodes.size(); +} + +SmNode* SmStructureNode::GetSubNode(size_t nIndex) +{ + return maSubNodes[nIndex]; +} + +SmNode* SmStructureNode::GetSubNodeBinMo(size_t nIndex) const +{ + if(GetType()==SmNodeType::BinDiagonal) + { + if (nIndex==1) + nIndex = 2; + else if (nIndex==2) + nIndex = 1; + } + return maSubNodes[nIndex]; +} + +void SmStructureNode::GetAccessibleText( OUStringBuffer &rText ) const +{ + ForEachNonNull(const_cast<SmStructureNode *>(this), + [&rText](SmNode *pNode) + { + if (pNode->IsVisible()) + pNode->SetAccessibleIndex(rText.getLength()); + pNode->GetAccessibleText( rText ); + }); +} + +void SmStructureNode::ClaimPaternity() +{ + ForEachNonNull(this, [this](SmNode *pNode){pNode->SetParent(this);}); +} + +int SmStructureNode::IndexOfSubNode(SmNode const * pSubNode) +{ + size_t nSize = GetNumSubNodes(); + for (size_t i = 0; i < nSize; i++) + if (pSubNode == GetSubNode(i)) + return i; + return -1; +} + +void SmStructureNode::SetSubNode(size_t nIndex, SmNode* pNode) +{ + size_t size = maSubNodes.size(); + if (size <= nIndex) + { + //Resize subnodes array + maSubNodes.resize(nIndex + 1); + //Set new slots to NULL except at nIndex + for (size_t i = size; i < nIndex; i++) + maSubNodes[i] = nullptr; + } + maSubNodes[nIndex] = pNode; + if (pNode) + pNode->SetParent(this); +} + +bool SmVisibleNode::IsVisible() const +{ + return true; +} + +size_t SmVisibleNode::GetNumSubNodes() const +{ + return 0; +} + +SmNode * SmVisibleNode::GetSubNode(size_t /*nIndex*/) +{ + return nullptr; +} + +void SmGraphicNode::GetAccessibleText( OUStringBuffer &rText ) const +{ + rText.append(GetToken().aText); +} + +void SmTableNode::Arrange(OutputDevice &rDev, const SmFormat &rFormat) + // arranges all subnodes in one column +{ + SmNode *pNode; + size_t nSize = GetNumSubNodes(); + + // make distance depend on font size + tools::Long nDist = +(rFormat.GetDistance(DIS_VERTICAL) + * GetFont().GetFontSize().Height()) / 100; + + if (nSize < 1) + return; + + // arrange subnodes and get maximum width of them + tools::Long nMaxWidth = 0, + nTmp; + for (size_t i = 0; i < nSize; ++i) + { + if (nullptr != (pNode = GetSubNode(i))) + { pNode->Arrange(rDev, rFormat); + if ((nTmp = pNode->GetItalicWidth()) > nMaxWidth) + nMaxWidth = nTmp; + } + } + + Point aPos; + SmRect::operator = (SmRect(nMaxWidth, 1)); + for (size_t i = 0; i < nSize; ++i) + { + if (nullptr != (pNode = GetSubNode(i))) + { const SmRect &rNodeRect = pNode->GetRect(); + const SmNode *pCoNode = pNode->GetLeftMost(); + RectHorAlign eHorAlign = pCoNode->GetRectHorAlign(); + + aPos = rNodeRect.AlignTo(*this, RectPos::Bottom, + eHorAlign, RectVerAlign::Baseline); + if (i) + aPos.AdjustY(nDist ); + pNode->MoveTo(aPos); + ExtendBy(rNodeRect, nSize > 1 ? RectCopyMBL::None : RectCopyMBL::Arg); + } + } + // #i972# + if (HasBaseline()) + mnFormulaBaseline = GetBaseline(); + else + { + SmTmpDevice aTmpDev (rDev, true); + aTmpDev.SetFont(GetFont()); + + SmRect aRect(aTmpDev, &rFormat, "a", GetFont().GetBorderWidth()); + mnFormulaBaseline = GetAlignM(); + // move from middle position by constant - distance + // between middle and baseline for single letter + mnFormulaBaseline += aRect.GetBaseline() - aRect.GetAlignM(); + } +} + +const SmNode * SmTableNode::GetLeftMost() const +{ + return this; +} + + +tools::Long SmTableNode::GetFormulaBaseline() const +{ + return mnFormulaBaseline; +} + + +/**************************************************************************/ + + +void SmLineNode::Prepare(const SmFormat &rFormat, const SmDocShell &rDocShell, int nDepth) +{ + SmNode::Prepare(rFormat, rDocShell, nDepth); + + // Here we use the 'FNT_VARIABLE' font since it's ascent and descent in general fit better + // to the rest of the formula compared to the 'FNT_MATH' font. + GetFont() = rFormat.GetFont(FNT_VARIABLE); + Flags() |= FontChangeMask::Face; +} + + +/**************************************************************************/ + + +void SmLineNode::Arrange(OutputDevice &rDev, const SmFormat &rFormat) + // arranges all subnodes in one row with some extra space between +{ + SmNode *pNode; + size_t nSize = GetNumSubNodes(); + for (size_t i = 0; i < nSize; ++i) + { + if (nullptr != (pNode = GetSubNode(i))) + pNode->Arrange(rDev, rFormat); + } + + SmTmpDevice aTmpDev (rDev, true); + aTmpDev.SetFont(GetFont()); + + if (nSize < 1) + { + // provide an empty rectangle with alignment parameters for the "current" + // font (in order to make "a^1 {}_2^3 a_4" work correct, that is, have the + // same sub-/supscript positions.) + //! be sure to use a character that has explicitly defined HiAttribut + //! line in rect.cxx such as 'a' in order to make 'vec a' look same to + //! 'vec {a}'. + SmRect::operator = (SmRect(aTmpDev, &rFormat, "a", + GetFont().GetBorderWidth())); + // make sure that the rectangle occupies (almost) no space + SetWidth(1); + SetItalicSpaces(0, 0); + return; + } + + // make distance depend on font size + tools::Long nDist = (rFormat.GetDistance(DIS_HORIZONTAL) * GetFont().GetFontSize().Height()) / 100; + if (!IsUseExtraSpaces()) + nDist = 0; + + Point aPos; + // copy the first node into LineNode and extend by the others + if (nullptr != (pNode = GetSubNode(0))) + SmRect::operator = (pNode->GetRect()); + + for (size_t i = 1; i < nSize; ++i) + { + if (nullptr != (pNode = GetSubNode(i))) + { + aPos = pNode->AlignTo(*this, RectPos::Right, RectHorAlign::Center, RectVerAlign::Baseline); + + // add horizontal space to the left for each but the first sub node + aPos.AdjustX(nDist ); + + pNode->MoveTo(aPos); + ExtendBy( *pNode, RectCopyMBL::Xor ); + } + } +} + + +/**************************************************************************/ + + +void SmExpressionNode::Arrange(OutputDevice &rDev, const SmFormat &rFormat) + // as 'SmLineNode::Arrange' but keeps alignment of leftmost subnode +{ + SmLineNode::Arrange(rDev, rFormat); + + // copy alignment of leftmost subnode if any + const SmNode *pNode = GetLeftMost(); + if (pNode) + SetRectHorAlign(pNode->GetRectHorAlign(), false); +} + + +/**************************************************************************/ + + +void SmUnHorNode::Arrange(OutputDevice &rDev, const SmFormat &rFormat) +{ + bool bIsPostfix = GetToken().eType == TFACT; + + SmNode *pNode0 = GetSubNode(0), + *pNode1 = GetSubNode(1); + SmNode *pOper = bIsPostfix ? pNode1 : pNode0, + *pBody = bIsPostfix ? pNode0 : pNode1; + assert(pOper); + assert(pBody); + + pOper->SetSize(Fraction (rFormat.GetRelSize(SIZ_OPERATOR), 100)); + pOper->Arrange(rDev, rFormat); + pBody->Arrange(rDev, rFormat); + + tools::Long nDist = (pOper->GetRect().GetWidth() * rFormat.GetDistance(DIS_HORIZONTAL)) / 100; + + SmRect::operator = (*pNode0); + + Point aPos = pNode1->AlignTo(*this, RectPos::Right, RectHorAlign::Center, RectVerAlign::Baseline); + aPos.AdjustX(nDist ); + pNode1->MoveTo(aPos); + ExtendBy(*pNode1, RectCopyMBL::Xor); +} + + +/**************************************************************************/ + +namespace { + +void lcl_GetHeightVerOffset(const SmRect &rRect, + tools::Long &rHeight, tools::Long &rVerOffset) + // calculate height and vertical offset of root sign suitable for 'rRect' +{ + rVerOffset = (rRect.GetBottom() - rRect.GetAlignB()) / 2; + rHeight = rRect.GetHeight() - rVerOffset; + + OSL_ENSURE(rHeight >= 0, "Sm : Ooops..."); + OSL_ENSURE(rVerOffset >= 0, "Sm : Ooops..."); +} + + +Point lcl_GetExtraPos(const SmRect &rRootSymbol, + const SmRect &rExtra) +{ + const Size &rSymSize = rRootSymbol.GetSize(); + + Point aPos = rRootSymbol.GetTopLeft() + + Point((rSymSize.Width() * 70) / 100, + (rSymSize.Height() * 52) / 100); + + // from this calculate topleft edge of 'rExtra' + aPos.AdjustX( -(rExtra.GetWidth() + rExtra.GetItalicRightSpace()) ); + aPos.AdjustY( -(rExtra.GetHeight()) ); + // if there's enough space move a bit less to the right + // examples: "nroot i a", "nroot j a" + // (it looks better if we don't use italic-spaces here) + tools::Long nX = rRootSymbol.GetLeft() + (rSymSize.Width() * 30) / 100; + if (aPos.X() > nX) + aPos.setX( nX ); + + return aPos; +} + +} + +void SmRootNode::Arrange(OutputDevice &rDev, const SmFormat &rFormat) +{ + //! pExtra needs to have the smaller index than pRootSym in order to + //! not to get the root symbol but the pExtra when clicking on it in the + //! GraphicWindow. (That is because of the simplicity of the algorithm + //! that finds the node corresponding to a mouseclick in the window.) + SmNode *pExtra = GetSubNode(0), + *pRootSym = GetSubNode(1), + *pBody = GetSubNode(2); + assert(pRootSym); + assert(pBody); + + pBody->Arrange(rDev, rFormat); + + tools::Long nHeight, + nVerOffset; + lcl_GetHeightVerOffset(*pBody, nHeight, nVerOffset); + nHeight += rFormat.GetDistance(DIS_ROOT) + * GetFont().GetFontSize().Height() / 100; + + if (nHeight < 0) + { + SAL_WARN("starmath", "negative height"); + nHeight = 0; + } + + // font specialist advised to change the width first + pRootSym->AdaptToY(rDev, nHeight); + pRootSym->AdaptToX(rDev, pBody->GetItalicWidth()); + + pRootSym->Arrange(rDev, rFormat); + + // Set the top and bottom of the root symbol to the top and bottom of its glyph bounding rect, + // to get accurate position of the root symbol. + SmRect rRootSymRect = pRootSym->AsGlyphRect(); + pRootSym->SetTop(rRootSymRect.GetTop()); + pRootSym->SetBottom(rRootSymRect.GetBottom()); + + Point aPos = pRootSym->AlignTo(*pBody, RectPos::Left, RectHorAlign::Center, RectVerAlign::Baseline); + //! override calculated vertical position + aPos.setY( pRootSym->GetTop() + pBody->GetBottom() - pRootSym->GetBottom() ); + aPos.AdjustY( -nVerOffset ); + pRootSym->MoveTo(aPos); + + if (pExtra) + { pExtra->SetSize(Fraction(rFormat.GetRelSize(SIZ_INDEX), 100)); + pExtra->Arrange(rDev, rFormat); + + aPos = lcl_GetExtraPos(*pRootSym, *pExtra); + pExtra->MoveTo(aPos); + } + + SmRect::operator = (*pBody); + ExtendBy(*pRootSym, RectCopyMBL::This); + if (pExtra) + ExtendBy(*pExtra, RectCopyMBL::This, true); +} + +/**************************************************************************/ + + +void SmBinHorNode::Arrange(OutputDevice &rDev, const SmFormat &rFormat) +{ + SmNode *pLeft = LeftOperand(), + *pOper = Symbol(), + *pRight = RightOperand(); + assert(pLeft); + assert(pOper); + assert(pRight); + + pOper->SetSize(Fraction (rFormat.GetRelSize(SIZ_OPERATOR), 100)); + + pLeft ->Arrange(rDev, rFormat); + pOper ->Arrange(rDev, rFormat); + pRight->Arrange(rDev, rFormat); + + const SmRect &rOpRect = pOper->GetRect(); + + tools::Long nMul; + if (o3tl::checked_multiply<tools::Long>(rOpRect.GetWidth(), rFormat.GetDistance(DIS_HORIZONTAL), nMul)) + { + SAL_WARN("starmath", "integer overflow"); + return; + } + + tools::Long nDist = nMul / 100; + + SmRect::operator = (*pLeft); + + Point aPos; + aPos = pOper->AlignTo(*this, RectPos::Right, RectHorAlign::Center, RectVerAlign::Baseline); + aPos.AdjustX(nDist ); + pOper->MoveTo(aPos); + ExtendBy(*pOper, RectCopyMBL::Xor); + + aPos = pRight->AlignTo(*this, RectPos::Right, RectHorAlign::Center, RectVerAlign::Baseline); + aPos.AdjustX(nDist ); + + pRight->MoveTo(aPos); + ExtendBy(*pRight, RectCopyMBL::Xor); +} + + +/**************************************************************************/ + + +void SmBinVerNode::Arrange(OutputDevice &rDev, const SmFormat &rFormat) +{ + SmNode *pNum = GetSubNode(0), + *pLine = GetSubNode(1), + *pDenom = GetSubNode(2); + assert(pNum); + assert(pLine); + assert(pDenom); + + bool bIsTextmode = rFormat.IsTextmode(); + if (bIsTextmode) + { + Fraction aFraction(rFormat.GetRelSize(SIZ_INDEX), 100); + pNum ->SetSize(aFraction); + pLine ->SetSize(aFraction); + pDenom->SetSize(aFraction); + } + + pNum ->Arrange(rDev, rFormat); + pDenom->Arrange(rDev, rFormat); + + tools::Long nFontHeight = GetFont().GetFontSize().Height(), + nExtLen = nFontHeight * rFormat.GetDistance(DIS_FRACTION) / 100, + nThick = nFontHeight * rFormat.GetDistance(DIS_STROKEWIDTH) / 100, + nWidth = std::max(pNum->GetItalicWidth(), pDenom->GetItalicWidth()), + nNumDist = bIsTextmode ? 0 : + nFontHeight * rFormat.GetDistance(DIS_NUMERATOR) / 100, + nDenomDist = bIsTextmode ? 0 : + nFontHeight * rFormat.GetDistance(DIS_DENOMINATOR) / 100; + + // font specialist advised to change the width first + pLine->AdaptToY(rDev, nThick); + pLine->AdaptToX(rDev, nWidth + 2 * nExtLen); + pLine->Arrange(rDev, rFormat); + + // get horizontal alignment for numerator + const SmNode *pLM = pNum->GetLeftMost(); + RectHorAlign eHorAlign = pLM->GetRectHorAlign(); + + // move numerator to its position + Point aPos = pNum->AlignTo(*pLine, RectPos::Top, eHorAlign, RectVerAlign::Baseline); + aPos.AdjustY( -nNumDist ); + pNum->MoveTo(aPos); + + // get horizontal alignment for denominator + pLM = pDenom->GetLeftMost(); + eHorAlign = pLM->GetRectHorAlign(); + + // move denominator to its position + aPos = pDenom->AlignTo(*pLine, RectPos::Bottom, eHorAlign, RectVerAlign::Baseline); + aPos.AdjustY(nDenomDist ); + pDenom->MoveTo(aPos); + + SmRect::operator = (*pNum); + ExtendBy(*pDenom, RectCopyMBL::None).ExtendBy(*pLine, RectCopyMBL::None, pLine->GetCenterY()); +} + +const SmNode * SmBinVerNode::GetLeftMost() const +{ + return this; +} + + +namespace { + +/// @return value of the determinant formed by the two points +double Det(const Point &rHeading1, const Point &rHeading2) +{ + return rHeading1.X() * rHeading2.Y() - rHeading1.Y() * rHeading2.X(); +} + + +/// Is true iff the point 'rPoint1' belongs to the straight line through 'rPoint2' +/// and has the direction vector 'rHeading2' +bool IsPointInLine(const Point &rPoint1, + const Point &rPoint2, const Point &rHeading2) +{ + assert(rHeading2 != Point()); + + bool bRes = false; + static const double eps = 5.0 * DBL_EPSILON; + + double fLambda; + if (std::abs(rHeading2.X()) > std::abs(rHeading2.Y())) + { + fLambda = (rPoint1.X() - rPoint2.X()) / static_cast<double>(rHeading2.X()); + bRes = fabs(rPoint1.Y() - (rPoint2.Y() + fLambda * rHeading2.Y())) < eps; + } + else + { + fLambda = (rPoint1.Y() - rPoint2.Y()) / static_cast<double>(rHeading2.Y()); + bRes = fabs(rPoint1.X() - (rPoint2.X() + fLambda * rHeading2.X())) < eps; + } + + return bRes; +} + + +sal_uInt16 GetLineIntersectionPoint(Point &rResult, + const Point& rPoint1, const Point &rHeading1, + const Point& rPoint2, const Point &rHeading2) +{ + assert(rHeading1 != Point()); + assert(rHeading2 != Point()); + + sal_uInt16 nRes = 1; + static const double eps = 5.0 * DBL_EPSILON; + + // are the direction vectors linearly dependent? + double fDet = Det(rHeading1, rHeading2); + if (fabs(fDet) < eps) + { + nRes = IsPointInLine(rPoint1, rPoint2, rHeading2) ? USHRT_MAX : 0; + rResult = nRes ? rPoint1 : Point(); + } + else + { + // here we do not pay attention to the computational accuracy + // (that would be more complicated and is not really worth it in this case) + double fLambda = ( (rPoint1.Y() - rPoint2.Y()) * rHeading2.X() + - (rPoint1.X() - rPoint2.X()) * rHeading2.Y()) + / fDet; + rResult = Point(rPoint1.X() + static_cast<tools::Long>(fLambda * rHeading1.X()), + rPoint1.Y() + static_cast<tools::Long>(fLambda * rHeading1.Y())); + } + + return nRes; +} + +} + + +/// @return position and size of the diagonal line +/// premise: SmRect of the node defines the limitation(!) consequently it has to be known upfront +void SmBinDiagonalNode::GetOperPosSize(Point &rPos, Size &rSize, + const Point &rDiagPoint, double fAngleDeg) const + +{ + double fAngleRad = basegfx::deg2rad(fAngleDeg); + tools::Long nRectLeft = GetItalicLeft(), + nRectRight = GetItalicRight(), + nRectTop = GetTop(), + nRectBottom = GetBottom(); + Point aRightHdg (100, 0), + aDownHdg (0, 100), + aDiagHdg ( static_cast<tools::Long>(100.0 * cos(fAngleRad)), + static_cast<tools::Long>(-100.0 * sin(fAngleRad)) ); + + tools::Long nLeft, nRight, nTop, nBottom; // margins of the rectangle for the diagonal + Point aPoint; + if (IsAscending()) + { + // determine top right corner + GetLineIntersectionPoint(aPoint, + Point(nRectLeft, nRectTop), aRightHdg, + rDiagPoint, aDiagHdg); + // is there a point of intersection with the top border? + if (aPoint.X() <= nRectRight) + { + nRight = aPoint.X(); + nTop = nRectTop; + } + else + { + // there has to be a point of intersection with the right border! + GetLineIntersectionPoint(aPoint, + Point(nRectRight, nRectTop), aDownHdg, + rDiagPoint, aDiagHdg); + + nRight = nRectRight; + nTop = aPoint.Y(); + } + + // determine bottom left corner + GetLineIntersectionPoint(aPoint, + Point(nRectLeft, nRectBottom), aRightHdg, + rDiagPoint, aDiagHdg); + // is there a point of intersection with the bottom border? + if (aPoint.X() >= nRectLeft) + { + nLeft = aPoint.X(); + nBottom = nRectBottom; + } + else + { + // there has to be a point of intersection with the left border! + GetLineIntersectionPoint(aPoint, + Point(nRectLeft, nRectTop), aDownHdg, + rDiagPoint, aDiagHdg); + + nLeft = nRectLeft; + nBottom = aPoint.Y(); + } + } + else + { + // determine top left corner + GetLineIntersectionPoint(aPoint, + Point(nRectLeft, nRectTop), aRightHdg, + rDiagPoint, aDiagHdg); + // is there a point of intersection with the top border? + if (aPoint.X() >= nRectLeft) + { + nLeft = aPoint.X(); + nTop = nRectTop; + } + else + { + // there has to be a point of intersection with the left border! + GetLineIntersectionPoint(aPoint, + Point(nRectLeft, nRectTop), aDownHdg, + rDiagPoint, aDiagHdg); + + nLeft = nRectLeft; + nTop = aPoint.Y(); + } + + // determine bottom right corner + GetLineIntersectionPoint(aPoint, + Point(nRectLeft, nRectBottom), aRightHdg, + rDiagPoint, aDiagHdg); + // is there a point of intersection with the bottom border? + if (aPoint.X() <= nRectRight) + { + nRight = aPoint.X(); + nBottom = nRectBottom; + } + else + { + // there has to be a point of intersection with the right border! + GetLineIntersectionPoint(aPoint, + Point(nRectRight, nRectTop), aDownHdg, + rDiagPoint, aDiagHdg); + + nRight = nRectRight; + nBottom = aPoint.Y(); + } + } + + rSize = Size(nRight - nLeft + 1, nBottom - nTop + 1); + rPos.setX( nLeft ); + rPos.setY( nTop ); +} + + +void SmBinDiagonalNode::Arrange(OutputDevice &rDev, const SmFormat &rFormat) +{ + // Both arguments have to get into the SubNodes before the Operator so that clicking + // within the GraphicWindow sets the FormulaCursor correctly (cf. SmRootNode) + SmNode *pLeft = GetSubNode(0), + *pRight = GetSubNode(1), + *pLine = GetSubNode(2); + assert(pLeft); + assert(pRight); + assert(pLine && pLine->GetType() == SmNodeType::PolyLine); + + SmPolyLineNode *pOper = static_cast<SmPolyLineNode *>(pLine); + assert(pOper); + + //! some routines being called extract some info from the OutputDevice's + //! font (eg the space to be used for borders OR the font name(!!)). + //! Thus the font should reflect the needs and has to be set! + SmTmpDevice aTmpDev (rDev, true); + aTmpDev.SetFont(GetFont()); + + pLeft->Arrange(aTmpDev, rFormat); + pRight->Arrange(aTmpDev, rFormat); + + // determine implicitly the values (incl. the margin) of the diagonal line + pOper->Arrange(aTmpDev, rFormat); + + tools::Long nDelta = pOper->GetWidth() * 8 / 10; + + // determine TopLeft position from the right argument + Point aPos; + aPos.setX( pLeft->GetItalicRight() + nDelta + pRight->GetItalicLeftSpace() ); + if (IsAscending()) + aPos.setY( pLeft->GetBottom() + nDelta ); + else + aPos.setY( pLeft->GetTop() - nDelta - pRight->GetHeight() ); + + pRight->MoveTo(aPos); + + // determine new baseline + tools::Long nTmpBaseline = IsAscending() ? (pLeft->GetBottom() + pRight->GetTop()) / 2 + : (pLeft->GetTop() + pRight->GetBottom()) / 2; + Point aLogCenter ((pLeft->GetItalicRight() + pRight->GetItalicLeft()) / 2, + nTmpBaseline); + + SmRect::operator = (*pLeft); + ExtendBy(*pRight, RectCopyMBL::None); + + + // determine position and size of diagonal line + Size aTmpSize; + GetOperPosSize(aPos, aTmpSize, aLogCenter, IsAscending() ? 60.0 : -60.0); + + // font specialist advised to change the width first + pOper->AdaptToY(aTmpDev, aTmpSize.Height()); + pOper->AdaptToX(aTmpDev, aTmpSize.Width()); + // and make it active + pOper->Arrange(aTmpDev, rFormat); + + pOper->MoveTo(aPos); + + ExtendBy(*pOper, RectCopyMBL::None, nTmpBaseline); +} + + +/**************************************************************************/ + + +void SmSubSupNode::Arrange(OutputDevice &rDev, const SmFormat &rFormat) +{ + OSL_ENSURE(GetNumSubNodes() == 1 + SUBSUP_NUM_ENTRIES, + "Sm: wrong number of subnodes"); + + SmNode *pBody = GetBody(); + assert(pBody); + + tools::Long nOrigHeight = pBody->GetFont().GetFontSize().Height(); + + pBody->Arrange(rDev, rFormat); + + const SmRect &rBodyRect = pBody->GetRect(); + SmRect::operator = (rBodyRect); + + // line that separates sub- and supscript rectangles + tools::Long nDelimLine = SmFromTo(GetAlignB(), GetAlignT(), 0.4); + + Point aPos; + tools::Long nDelta, nDist; + + // iterate over all possible sub-/supscripts + SmRect aTmpRect (rBodyRect); + for (int i = 0; i < SUBSUP_NUM_ENTRIES; i++) + { + SmSubSup eSubSup = static_cast<SmSubSup>(i); + SmNode *pSubSup = GetSubSup(eSubSup); + + if (!pSubSup) + continue; + + // switch position of limits if we are in textmode + if (rFormat.IsTextmode() && (GetToken().nGroup & TG::Limit)) + switch (eSubSup) + { case CSUB: eSubSup = RSUB; break; + case CSUP: eSubSup = RSUP; break; + default: + break; + } + + // prevent sub-/supscripts from diminishing in size + // (as would be in "a_{1_{2_{3_4}}}") + if (GetFont().GetFontSize().Height() > rFormat.GetBaseSize().Height() / 3) + { + sal_uInt16 nIndex = (eSubSup == CSUB || eSubSup == CSUP) ? + SIZ_LIMITS : SIZ_INDEX; + Fraction aFraction ( rFormat.GetRelSize(nIndex), 100 ); + pSubSup->SetSize(aFraction); + } + + pSubSup->Arrange(rDev, rFormat); + + bool bIsTextmode = rFormat.IsTextmode(); + nDist = 0; + + //! be sure that CSUB, CSUP are handled before the other cases! + switch (eSubSup) + { case RSUB : + case LSUB : + if (!bIsTextmode) + nDist = nOrigHeight + * rFormat.GetDistance(DIS_SUBSCRIPT) / 100; + aPos = pSubSup->GetRect().AlignTo(aTmpRect, + eSubSup == LSUB ? RectPos::Left : RectPos::Right, + RectHorAlign::Center, RectVerAlign::Bottom); + aPos.AdjustY(nDist ); + nDelta = nDelimLine - aPos.Y(); + if (nDelta > 0) + aPos.AdjustY(nDelta ); + break; + case RSUP : + case LSUP : + if (!bIsTextmode) + nDist = nOrigHeight + * rFormat.GetDistance(DIS_SUPERSCRIPT) / 100; + aPos = pSubSup->GetRect().AlignTo(aTmpRect, + eSubSup == LSUP ? RectPos::Left : RectPos::Right, + RectHorAlign::Center, RectVerAlign::Top); + aPos.AdjustY( -nDist ); + nDelta = aPos.Y() + pSubSup->GetHeight() - nDelimLine; + if (nDelta > 0) + aPos.AdjustY( -nDelta ); + break; + case CSUB : + if (!bIsTextmode) + nDist = nOrigHeight + * rFormat.GetDistance(DIS_LOWERLIMIT) / 100; + aPos = pSubSup->GetRect().AlignTo(rBodyRect, RectPos::Bottom, + RectHorAlign::Center, RectVerAlign::Baseline); + aPos.AdjustY(nDist ); + break; + case CSUP : + if (!bIsTextmode) + nDist = nOrigHeight + * rFormat.GetDistance(DIS_UPPERLIMIT) / 100; + aPos = pSubSup->GetRect().AlignTo(rBodyRect, RectPos::Top, + RectHorAlign::Center, RectVerAlign::Baseline); + aPos.AdjustY( -nDist ); + break; + } + + pSubSup->MoveTo(aPos); + ExtendBy(*pSubSup, RectCopyMBL::This, true); + + // update rectangle to which RSUB, RSUP, LSUB, LSUP + // will be aligned to + if (eSubSup == CSUB || eSubSup == CSUP) + aTmpRect = *this; + } +} + +/**************************************************************************/ + +void SmBraceNode::Arrange(OutputDevice &rDev, const SmFormat &rFormat) +{ + SmNode *pLeft = OpeningBrace(), + *pBody = Body(), + *pRight = ClosingBrace(); + assert(pLeft); + assert(pBody); + assert(pRight); + + pBody->Arrange(rDev, rFormat); + + bool bIsScaleNormal = rFormat.IsScaleNormalBrackets(), + bScale = pBody->GetHeight() > 0 && + (GetScaleMode() == SmScaleMode::Height || bIsScaleNormal), + bIsABS = GetToken().eType == TABS; + + tools::Long nFaceHeight = GetFont().GetFontSize().Height(); + + // determine oversize in % + sal_uInt16 nPerc = 0; + if (!bIsABS && bScale) + { // in case of oversize braces... + sal_uInt16 nIndex = GetScaleMode() == SmScaleMode::Height ? + DIS_BRACKETSIZE : DIS_NORMALBRACKETSIZE; + nPerc = rFormat.GetDistance(nIndex); + } + + // determine the height for the braces + tools::Long nBraceHeight; + if (bScale) + { + nBraceHeight = pBody->GetType() == SmNodeType::Bracebody ? + static_cast<SmBracebodyNode *>(pBody)->GetBodyHeight() + : pBody->GetHeight(); + nBraceHeight += 2 * (nBraceHeight * nPerc / 100); + } + else + nBraceHeight = nFaceHeight; + + // distance to the argument + nPerc = bIsABS ? 0 : rFormat.GetDistance(DIS_BRACKETSPACE); + tools::Long nDist = nFaceHeight * nPerc / 100; + + // if wanted, scale the braces to the wanted size + if (bScale) + { + Size aTmpSize (pLeft->GetFont().GetFontSize()); + OSL_ENSURE(pRight->GetFont().GetFontSize() == aTmpSize, + "Sm : different font sizes"); + aTmpSize.setWidth( std::min(nBraceHeight * 60 / 100, + rFormat.GetBaseSize().Height() * 3 / 2) ); + // correction factor since change from StarMath to OpenSymbol font + // because of the different font width in the FontMetric + aTmpSize.setWidth( aTmpSize.Width() * 182 ); + aTmpSize.setWidth( aTmpSize.Width() / 267 ); + + sal_Unicode cChar = pLeft->GetToken().cMathChar[0]; + if (cChar != MS_LINE && cChar != MS_DLINE && + cChar != MS_VERTLINE && cChar != MS_DVERTLINE) + pLeft ->GetFont().SetSize(aTmpSize); + + cChar = pRight->GetToken().cMathChar[0]; + if (cChar != MS_LINE && cChar != MS_DLINE && + cChar != MS_VERTLINE && cChar != MS_DVERTLINE) + pRight->GetFont().SetSize(aTmpSize); + + pLeft ->AdaptToY(rDev, nBraceHeight); + pRight->AdaptToY(rDev, nBraceHeight); + } + + pLeft ->Arrange(rDev, rFormat); + pRight->Arrange(rDev, rFormat); + + // required in order to make "\(a\) - (a) - left ( a right )" look alright + RectVerAlign eVerAlign = bScale ? RectVerAlign::CenterY : RectVerAlign::Baseline; + + Point aPos; + aPos = pLeft->AlignTo(*pBody, RectPos::Left, RectHorAlign::Center, eVerAlign); + aPos.AdjustX( -nDist ); + pLeft->MoveTo(aPos); + + aPos = pRight->AlignTo(*pBody, RectPos::Right, RectHorAlign::Center, eVerAlign); + aPos.AdjustX(nDist ); + pRight->MoveTo(aPos); + + SmRect::operator = (*pBody); + ExtendBy(*pLeft, RectCopyMBL::This).ExtendBy(*pRight, RectCopyMBL::This); +} + + +/**************************************************************************/ + + +void SmBracebodyNode::Arrange(OutputDevice &rDev, const SmFormat &rFormat) +{ + size_t nNumSubNodes = GetNumSubNodes(); + if (nNumSubNodes == 0) + return; + + // arrange arguments + for (size_t i = 0; i < nNumSubNodes; i += 2) + GetSubNode(i)->Arrange(rDev, rFormat); + + // build reference rectangle with necessary info for vertical alignment + SmRect aRefRect (*GetSubNode(0)); + for (size_t i = 0; i < nNumSubNodes; i += 2) + { + SmRect aTmpRect (*GetSubNode(i)); + Point aPos = aTmpRect.AlignTo(aRefRect, RectPos::Right, RectHorAlign::Center, RectVerAlign::Baseline); + aTmpRect.MoveTo(aPos); + aRefRect.ExtendBy(aTmpRect, RectCopyMBL::Xor); + } + + mnBodyHeight = aRefRect.GetHeight(); + + // scale separators to required height and arrange them + bool bScale = GetScaleMode() == SmScaleMode::Height || rFormat.IsScaleNormalBrackets(); + tools::Long nHeight = bScale ? aRefRect.GetHeight() : GetFont().GetFontSize().Height(); + sal_uInt16 nIndex = GetScaleMode() == SmScaleMode::Height ? + DIS_BRACKETSIZE : DIS_NORMALBRACKETSIZE; + sal_uInt16 nPerc = rFormat.GetDistance(nIndex); + if (bScale) + nHeight += 2 * (nHeight * nPerc / 100); + for (size_t i = 1; i < nNumSubNodes; i += 2) + { + SmNode *pNode = GetSubNode(i); + pNode->AdaptToY(rDev, nHeight); + pNode->Arrange(rDev, rFormat); + } + + // horizontal distance between argument and brackets or separators + tools::Long nDist = GetFont().GetFontSize().Height() + * rFormat.GetDistance(DIS_BRACKETSPACE) / 100; + + SmNode *pLeft = GetSubNode(0); + SmRect::operator = (*pLeft); + for (size_t i = 1; i < nNumSubNodes; ++i) + { + bool bIsSeparator = i % 2 != 0; + RectVerAlign eVerAlign = bIsSeparator ? RectVerAlign::CenterY : RectVerAlign::Baseline; + + SmNode *pRight = GetSubNode(i); + Point aPosX = pRight->AlignTo(*pLeft, RectPos::Right, RectHorAlign::Center, eVerAlign), + aPosY = pRight->AlignTo(aRefRect, RectPos::Right, RectHorAlign::Center, eVerAlign); + aPosX.AdjustX(nDist ); + + pRight->MoveTo(Point(aPosX.X(), aPosY.Y())); + ExtendBy(*pRight, bIsSeparator ? RectCopyMBL::This : RectCopyMBL::Xor); + + pLeft = pRight; + } +} + + +/**************************************************************************/ + + +void SmVerticalBraceNode::Arrange(OutputDevice &rDev, const SmFormat &rFormat) +{ + SmNode *pBody = Body(), + *pBrace = Brace(), + *pScript = Script(); + assert(pBody); + assert(pBrace); + assert(pScript); + + SmTmpDevice aTmpDev (rDev, true); + aTmpDev.SetFont(GetFont()); + + pBody->Arrange(aTmpDev, rFormat); + + // size is the same as for limits for this part + pScript->SetSize( Fraction( rFormat.GetRelSize(SIZ_LIMITS), 100 ) ); + // braces are a bit taller than usually + pBrace ->SetSize( Fraction(3, 2) ); + + tools::Long nItalicWidth = pBody->GetItalicWidth(); + if (nItalicWidth > 0) + pBrace->AdaptToX(aTmpDev, nItalicWidth); + + pBrace ->Arrange(aTmpDev, rFormat); + pScript->Arrange(aTmpDev, rFormat); + + // determine the relative position and the distances between each other + RectPos eRectPos; + tools::Long nFontHeight = pBody->GetFont().GetFontSize().Height(); + tools::Long nDistBody = nFontHeight * rFormat.GetDistance(DIS_ORNAMENTSIZE), + nDistScript = nFontHeight; + if (GetToken().eType == TOVERBRACE) + { + eRectPos = RectPos::Top; + nDistBody = - nDistBody; + nDistScript *= - rFormat.GetDistance(DIS_UPPERLIMIT); + } + else // TUNDERBRACE + { + eRectPos = RectPos::Bottom; + nDistScript *= + rFormat.GetDistance(DIS_LOWERLIMIT); + } + nDistBody /= 100; + nDistScript /= 100; + + Point aPos = pBrace->AlignTo(*pBody, eRectPos, RectHorAlign::Center, RectVerAlign::Baseline); + aPos.AdjustY(nDistBody ); + pBrace->MoveTo(aPos); + + aPos = pScript->AlignTo(*pBrace, eRectPos, RectHorAlign::Center, RectVerAlign::Baseline); + aPos.AdjustY(nDistScript ); + pScript->MoveTo(aPos); + + SmRect::operator = (*pBody); + ExtendBy(*pBrace, RectCopyMBL::This).ExtendBy(*pScript, RectCopyMBL::This); +} + + +/**************************************************************************/ + + +SmNode * SmOperNode::GetSymbol() +{ + SmNode *pNode = GetSubNode(0); + assert(pNode); + + if (pNode->GetType() == SmNodeType::SubSup) + pNode = static_cast<SmSubSupNode *>(pNode)->GetBody(); + + OSL_ENSURE(pNode, "Sm: NULL pointer!"); + return pNode; +} + + +tools::Long SmOperNode::CalcSymbolHeight(const SmNode &rSymbol, + const SmFormat &rFormat) const + // returns the font height to be used for operator-symbol +{ + tools::Long nHeight = GetFont().GetFontSize().Height(); + + SmTokenType eTmpType = GetToken().eType; + if (eTmpType == TLIM || eTmpType == TLIMINF || eTmpType == TLIMSUP) + return nHeight; + + if (!rFormat.IsTextmode()) + { + // set minimum size () + nHeight += (nHeight * 20) / 100; + + nHeight += nHeight + * rFormat.GetDistance(DIS_OPERATORSIZE) / 100; + nHeight = nHeight * 686 / 845; + } + + // correct user-defined symbols to match height of sum from used font + if (rSymbol.GetToken().eType == TSPECIAL) + nHeight = nHeight * 845 / 686; + + return nHeight; +} + + +void SmOperNode::Arrange(OutputDevice &rDev, const SmFormat &rFormat) +{ + SmNode *pOper = GetSubNode(0); + SmNode *pBody = GetSubNode(1); + + assert(pOper); + assert(pBody); + + SmNode *pSymbol = GetSymbol(); + pSymbol->SetSize(Fraction(CalcSymbolHeight(*pSymbol, rFormat), + pSymbol->GetFont().GetFontSize().Height())); + + pBody->Arrange(rDev, rFormat); + bool bDynamicallySized = false; + if (pSymbol->GetToken().eType == TINTD) + { + tools::Long nBodyHeight = pBody->GetHeight(); + tools::Long nFontHeight = pSymbol->GetFont().GetFontSize().Height(); + if (nFontHeight < nBodyHeight) + { + pSymbol->SetSize(Fraction(nBodyHeight, nFontHeight)); + bDynamicallySized = true; + } + } + pOper->Arrange(rDev, rFormat); + + tools::Long nOrigHeight = GetFont().GetFontSize().Height(), + nDist = nOrigHeight + * rFormat.GetDistance(DIS_OPERATORSPACE) / 100; + + Point aPos = pOper->AlignTo(*pBody, RectPos::Left, RectHorAlign::Center, bDynamicallySized ? RectVerAlign::CenterY : RectVerAlign::Mid); + aPos.AdjustX( -nDist ); + pOper->MoveTo(aPos); + + SmRect::operator = (*pBody); + ExtendBy(*pOper, RectCopyMBL::This); +} + + +/**************************************************************************/ + + +void SmAlignNode::Arrange(OutputDevice &rDev, const SmFormat &rFormat) + // set alignment within the entire subtree (including current node) +{ + assert(GetNumSubNodes() == 1); + + SmNode *pNode = GetSubNode(0); + assert(pNode); + + RectHorAlign eHorAlign = RectHorAlign::Center; + switch (GetToken().eType) + { + case TALIGNL: eHorAlign = RectHorAlign::Left; break; + case TALIGNC: eHorAlign = RectHorAlign::Center; break; + case TALIGNR: eHorAlign = RectHorAlign::Right; break; + default: + break; + } + SetRectHorAlign(eHorAlign); + + pNode->Arrange(rDev, rFormat); + + SmRect::operator = (pNode->GetRect()); +} + + +/**************************************************************************/ + + +void SmAttributeNode::Arrange(OutputDevice &rDev, const SmFormat &rFormat) +{ + SmNode *pAttr = Attribute(), + *pBody = Body(); + assert(pBody); + assert(pAttr); + + pBody->Arrange(rDev, rFormat); + + if (GetScaleMode() == SmScaleMode::Width) + pAttr->AdaptToX(rDev, pBody->GetItalicWidth()); + pAttr->Arrange(rDev, rFormat); + + // get relative position of attribute + RectVerAlign eVerAlign; + tools::Long nDist = 0; + switch (GetToken().eType) + { case TUNDERLINE : + eVerAlign = RectVerAlign::AttributeLo; + break; + case TOVERSTRIKE : + eVerAlign = RectVerAlign::AttributeMid; + break; + default : + eVerAlign = RectVerAlign::AttributeHi; + if (pBody->GetType() == SmNodeType::Attribute) + nDist = GetFont().GetFontSize().Height() + * rFormat.GetDistance(DIS_ORNAMENTSPACE) / 100; + } + Point aPos = pAttr->AlignTo(*pBody, RectPos::Attribute, RectHorAlign::Center, eVerAlign); + aPos.AdjustY( -nDist ); + pAttr->MoveTo(aPos); + + SmRect::operator = (*pBody); + ExtendBy(*pAttr, RectCopyMBL::This, true); +} + +void SmFontNode::Prepare(const SmFormat &rFormat, const SmDocShell &rDocShell, int nDepth) +{ + //! prepare subnodes first + SmNode::Prepare(rFormat, rDocShell, nDepth); + + int nFnt = -1; + switch (GetToken().eType) + { + case TFIXED: nFnt = FNT_FIXED; break; + case TSANS: nFnt = FNT_SANS; break; + case TSERIF: nFnt = FNT_SERIF; break; + default: + break; + } + if (nFnt != -1) + { GetFont() = rFormat.GetFont( sal::static_int_cast< sal_uInt16 >(nFnt) ); + SetFont(GetFont()); + } + + //! prevent overwrites of this font by 'Arrange' or 'SetFont' calls of + //! other font nodes (those with lower depth in the tree) + Flags() |= FontChangeMask::Face; +} + +void SmFontNode::Arrange(OutputDevice &rDev, const SmFormat &rFormat) +{ + SmNode *pNode = GetSubNode(1); + assert(pNode); + sal_uInt32 nc; + + switch (GetToken().eType) + { case TSIZE : + pNode->SetFontSize(maFontSize, meSizeType); + break; + case TSANS : + case TSERIF : + case TFIXED : + pNode->SetFont(GetFont()); + break; + case TUNKNOWN : break; // no assertion on "font <?> <?>" + + case TPHANTOM : SetPhantom(true); break; + case TBOLD : SetAttribute(FontAttribute::Bold); break; + case TITALIC : SetAttribute(FontAttribute::Italic); break; + case TNBOLD : ClearAttribute(FontAttribute::Bold); break; + case TNITALIC : ClearAttribute(FontAttribute::Italic); break; + + // Using HTML CSS Level 1 standard + case TRGB : + case TRGBA : + case THTMLCOL : + case TMATHMLCOL : + case TDVIPSNAMESCOL: + case TICONICCOL : + case THEX : + nc = GetToken().cMathChar.toUInt32(16); + SetColor(Color(ColorTransparency, nc)); + break; + + default: + SAL_WARN("starmath", "unknown case"); + } + + pNode->Arrange(rDev, rFormat); + + SmRect::operator = (pNode->GetRect()); +} + +/**************************************************************************/ + + +SmPolyLineNode::SmPolyLineNode(const SmToken &rNodeToken) + : SmGraphicNode(SmNodeType::PolyLine, rNodeToken) + , maPoly(2) + , mnWidth(0) +{ +} + + +void SmPolyLineNode::AdaptToX(OutputDevice &/*rDev*/, sal_uLong nNewWidth) +{ + maToSize.setWidth( nNewWidth ); +} + + +void SmPolyLineNode::AdaptToY(OutputDevice &/*rDev*/, sal_uLong nNewHeight) +{ + GetFont().FreezeBorderWidth(); + maToSize.setHeight( nNewHeight ); +} + + +void SmPolyLineNode::Arrange(OutputDevice &rDev, const SmFormat &rFormat) +{ + //! some routines being called extract some info from the OutputDevice's + //! font (eg the space to be used for borders OR the font name(!!)). + //! Thus the font should reflect the needs and has to be set! + SmTmpDevice aTmpDev (rDev, true); + aTmpDev.SetFont(GetFont()); + + tools::Long nBorderwidth = GetFont().GetBorderWidth(); + + // create polygon using both endpoints + assert(maPoly.GetSize() == 2); + Point aPointA, aPointB; + if (GetToken().eType == TWIDESLASH) + { + aPointA.setX( nBorderwidth ); + aPointA.setY( maToSize.Height() - nBorderwidth ); + aPointB.setX( maToSize.Width() - nBorderwidth ); + aPointB.setY( nBorderwidth ); + } + else + { + OSL_ENSURE(GetToken().eType == TWIDEBACKSLASH, "Sm : unexpected token"); + aPointA.setX( nBorderwidth ); + aPointA.setY( nBorderwidth ); + aPointB.setX( maToSize.Width() - nBorderwidth ); + aPointB.setY( maToSize.Height() - nBorderwidth ); + } + maPoly.SetPoint(aPointA, 0); + maPoly.SetPoint(aPointB, 1); + + tools::Long nThick = GetFont().GetFontSize().Height() + * rFormat.GetDistance(DIS_STROKEWIDTH) / 100; + mnWidth = nThick + 2 * nBorderwidth; + + SmRect::operator = (SmRect(maToSize.Width(), maToSize.Height())); +} + + +/**************************************************************************/ + +void SmRootSymbolNode::AdaptToX(OutputDevice &/*rDev*/, sal_uLong nWidth) +{ + mnBodyWidth = nWidth; +} + + +void SmRootSymbolNode::AdaptToY(OutputDevice &rDev, sal_uLong nHeight) +{ + // some additional length so that the horizontal + // bar will be positioned above the argument + SmMathSymbolNode::AdaptToY(rDev, nHeight + nHeight / 10); +} + + +/**************************************************************************/ + + +void SmRectangleNode::AdaptToX(OutputDevice &/*rDev*/, sal_uLong nWidth) +{ + maToSize.setWidth( nWidth ); +} + + +void SmRectangleNode::AdaptToY(OutputDevice &/*rDev*/, sal_uLong nHeight) +{ + GetFont().FreezeBorderWidth(); + maToSize.setHeight( nHeight ); +} + + +void SmRectangleNode::Arrange(OutputDevice &rDev, const SmFormat &/*rFormat*/) +{ + tools::Long nFontHeight = GetFont().GetFontSize().Height(); + tools::Long nWidth = maToSize.Width(), + nHeight = maToSize.Height(); + if (nHeight == 0) + nHeight = nFontHeight / 30; + if (nWidth == 0) + nWidth = nFontHeight / 3; + + SmTmpDevice aTmpDev (rDev, true); + aTmpDev.SetFont(GetFont()); + + // add some borderspace + sal_uLong nTmpBorderWidth = GetFont().GetBorderWidth(); + nHeight += 2 * nTmpBorderWidth; + + //! use this method in order to have 'SmRect::HasAlignInfo() == true' + //! and thus having the attribute-fences updated in 'SmRect::ExtendBy' + SmRect::operator = (SmRect(nWidth, nHeight)); +} + + +/**************************************************************************/ + + +SmTextNode::SmTextNode( SmNodeType eNodeType, const SmToken &rNodeToken, sal_uInt16 nFontDescP ) + : SmVisibleNode(eNodeType, rNodeToken) + , mnFontDesc(nFontDescP) + , mnSelectionStart(0) + , mnSelectionEnd(0) +{ +} + +SmTextNode::SmTextNode( const SmToken &rNodeToken, sal_uInt16 nFontDescP ) + : SmVisibleNode(SmNodeType::Text, rNodeToken) + , mnFontDesc(nFontDescP) + , mnSelectionStart(0) + , mnSelectionEnd(0) +{ +} + +void SmTextNode::ChangeText(const OUString &rText) { + maText = rText; + GetToken().aText = rText; + AdjustFontDesc(); +} + +void SmTextNode::Prepare(const SmFormat &rFormat, const SmDocShell &rDocShell, int nDepth) +{ + SmNode::Prepare(rFormat, rDocShell, nDepth); + + // default setting for horizontal alignment of nodes with TTEXT + // content is as alignl (cannot be done in Arrange since it would + // override the settings made by an SmAlignNode before) + if (TTEXT == GetToken().eType) + SetRectHorAlign( RectHorAlign::Left ); + + maText = GetToken().aText; + GetFont() = rFormat.GetFont(GetFontDesc()); + + if (IsItalic( GetFont() )) + Attributes() |= FontAttribute::Italic; + if (IsBold( GetFont() )) + Attributes() |= FontAttribute::Bold; + + // special handling for ':' where it is a token on its own and is likely + // to be used for mathematical notations. (E.g. a:b = 2:3) + // In that case it should not be displayed in italic. + if (maText.getLength() == 1 && GetToken().aText[0] == ':') + Attributes() &= ~FontAttribute::Italic; + + // Arabic text should not be italic, so we check for any character in Arabic script and + // remove italic attribute. + if (!maText.isEmpty()) + { + sal_Int32 nIndex = 0; + while (nIndex < maText.getLength()) + { + sal_uInt32 cChar = maText.iterateCodePoints(&nIndex); + if (u_getIntPropertyValue(cChar, UCHAR_SCRIPT) == USCRIPT_ARABIC) + { + Attributes() &= ~FontAttribute::Italic; + break; + } + } + } +}; + + +void SmTextNode::Arrange(OutputDevice &rDev, const SmFormat &rFormat) +{ + PrepareAttributes(); + + sal_uInt16 nSizeDesc = GetFontDesc() == FNT_FUNCTION ? + SIZ_FUNCTION : SIZ_TEXT; + GetFont() *= Fraction (rFormat.GetRelSize(nSizeDesc), 100); + + SmTmpDevice aTmpDev (rDev, true); + aTmpDev.SetFont(GetFont()); + + SmRect::operator = (SmRect(aTmpDev, &rFormat, maText, GetFont().GetBorderWidth())); +} + +void SmTextNode::GetAccessibleText( OUStringBuffer &rText ) const +{ + rText.append(maText); +} + +void SmTextNode::AdjustFontDesc() +{ + if (GetToken().nGroup == TG::Function) mnFontDesc = FNT_FUNCTION; + else if (GetToken().eType == TTEXT) mnFontDesc = FNT_TEXT; + else { + sal_Unicode firstChar = maText[0]; + if( ('0' <= firstChar && firstChar <= '9') || firstChar == '.' || firstChar == ',') + mnFontDesc = FNT_NUMBER; + else mnFontDesc = FNT_VARIABLE; + } +} + +sal_Unicode SmTextNode::ConvertSymbolToUnicode(sal_Unicode nIn) +{ + //Find the best match in accepted unicode for our private area symbols + static const sal_Unicode aStarMathPrivateToUnicode[] = + { + 0x2030, 0xF613, 0xF612, 0x002B, 0x003C, 0x003E, 0xE425, 0xE421, 0xE088, 0x2208, + 0x0192, 0x2026, 0x2192, 0x221A, 0x221A, 0x221A, 0xE090, 0x005E, 0x02C7, 0x02D8, + 0x00B4, 0x0060, 0x02DC, 0x00AF, 0x0362, 0xE099, 0xE09A, 0x20DB, 0xE09C, 0xE09D, + 0x0028, 0x0029, 0x2220, 0x22AF, 0xE0A2, 0xE0A3, 0xE0A4, 0xE0A5, 0xE0A6, 0xE0A7, + 0x002F, 0x005C, 0x274F, 0xE0AB, 0x0393, 0x0394, 0x0398, 0x039b, 0x039e, 0x03A0, + 0x03a3, 0x03a5, 0x03a6, 0x03a8, 0x03A9, 0x03B1, 0x03B2, 0x03b3, 0x03b4, 0x03b5, + 0x03b6, 0x03b7, 0x03b8, 0x03b9, 0x03ba, 0x03bb, 0x03bc, 0x03bd, 0x03be, 0x03bf, + 0x03c0, 0x03c1, 0x03c3, 0x03c4, 0x03c5, 0x03c6, 0x03c7, 0x03c8, 0x03c9, 0x03b5, + 0x03d1, 0x03d6, 0xE0D2, 0x03db, 0x2118, 0x2202, 0x2129, 0xE0D7, 0xE0D8, 0x22A4, + 0xE0DA, 0x2190, 0x2191, 0x2193 + }; + if ((nIn >= 0xE080) && (nIn <= 0xE0DD)) + nIn = aStarMathPrivateToUnicode[nIn-0xE080]; + + //For whatever unicode glyph that equation editor doesn't ship with that + //we have a possible match we can munge it to. + switch (nIn) + { + case 0x2223: + nIn = '|'; + break; + default: + break; + } + + return nIn; +} + +/**************************************************************************/ + +void SmMatrixNode::Arrange(OutputDevice &rDev, const SmFormat &rFormat) +{ + SmNode *pNode; + + // initialize array that is to hold the maximum widths of all + // elements (subnodes) in that column. + std::vector<tools::Long> aColWidth(mnNumCols); + + // arrange subnodes and calculate the above arrays contents + size_t nNodes = GetNumSubNodes(); + for (size_t i = 0; i < nNodes; ++i) + { + size_t nIdx = nNodes - 1 - i; + if (nullptr != (pNode = GetSubNode(nIdx))) + { + pNode->Arrange(rDev, rFormat); + int nCol = nIdx % mnNumCols; + aColWidth[nCol] = std::max(aColWidth[nCol], pNode->GetItalicWidth()); + } + } + + // norm distance from which the following two are calculated + const tools::Long nNormDist = 3 * GetFont().GetFontSize().Height(); + + // define horizontal and vertical minimal distances that separate + // the elements + tools::Long nHorDist = nNormDist * rFormat.GetDistance(DIS_MATRIXCOL) / 100, + nVerDist = nNormDist * rFormat.GetDistance(DIS_MATRIXROW) / 100; + + // build array that holds the leftmost position for each column + std::vector<tools::Long> aColLeft(mnNumCols); + tools::Long nX = 0; + for (size_t j = 0; j < mnNumCols; ++j) + { + aColLeft[j] = nX; + nX += aColWidth[j] + nHorDist; + } + + SmRect::operator = (SmRect()); + for (size_t i = 0; i < mnNumRows; ++i) + { + Point aPos; + SmRect aLineRect; + for (size_t j = 0; j < mnNumCols; ++j) + { + SmNode *pTmpNode = GetSubNode(i * mnNumCols + j); + assert(pTmpNode); + + const SmRect &rNodeRect = pTmpNode->GetRect(); + + // align all baselines in that row if possible + aPos = rNodeRect.AlignTo(aLineRect, RectPos::Right, RectHorAlign::Center, RectVerAlign::Baseline); + + // get horizontal alignment + const SmNode *pCoNode = pTmpNode->GetLeftMost(); + RectHorAlign eHorAlign = pCoNode->GetRectHorAlign(); + + // calculate horizontal position of element depending on column + // and horizontal alignment + switch (eHorAlign) + { case RectHorAlign::Left: + aPos.setX( aColLeft[j] ); + break; + case RectHorAlign::Center: + aPos.setX( rNodeRect.GetLeft() + aColLeft[j] + + aColWidth[j] / 2 + - rNodeRect.GetItalicCenterX() ); + break; + case RectHorAlign::Right: + aPos.setX( aColLeft[j] + + aColWidth[j] - rNodeRect.GetItalicWidth() ); + break; + default: + assert(false); + } + + pTmpNode->MoveTo(aPos); + aLineRect.ExtendBy(rNodeRect, RectCopyMBL::Xor); + } + + aPos = aLineRect.AlignTo(*this, RectPos::Bottom, RectHorAlign::Center, RectVerAlign::Baseline); + if (i > 0) + aPos.AdjustY(nVerDist ); + + // move 'aLineRect' and rectangles in that line to final position + Point aDelta(0, // since horizontal alignment is already done + aPos.Y() - aLineRect.GetTop()); + aLineRect.Move(aDelta); + for (size_t j = 0; j < mnNumCols; ++j) + { + if (nullptr != (pNode = GetSubNode(i * mnNumCols + j))) + pNode->Move(aDelta); + } + + ExtendBy(aLineRect, RectCopyMBL::None); + } +} + +const SmNode * SmMatrixNode::GetLeftMost() const +{ + return this; +} + + +/**************************************************************************/ + + +SmMathSymbolNode::SmMathSymbolNode(const SmToken &rNodeToken) +: SmSpecialNode(SmNodeType::Math, rNodeToken, FNT_MATH) +{ + SetText(GetToken().cMathChar); +} + +void SmMathSymbolNode::AdaptToX(OutputDevice &rDev, sal_uLong nWidth) +{ + // Since there is no function to do this, we try to approximate it: + Size aFntSize (GetFont().GetFontSize()); + + //! however the result is a bit better with 'nWidth' as initial font width + aFntSize.setWidth( nWidth ); + GetFont().SetSize(aFntSize); + + SmTmpDevice aTmpDev (rDev, true); + aTmpDev.SetFont(GetFont()); + + // get denominator of error factor for width + tools::Long nTmpBorderWidth = GetFont().GetBorderWidth(); + tools::Long nDenom = SmRect(aTmpDev, nullptr, GetText(), nTmpBorderWidth).GetItalicWidth(); + + // scale fontwidth with this error factor + aFntSize.setWidth( aFntSize.Width() * nWidth ); + aFntSize.setWidth( aFntSize.Width() / ( nDenom ? nDenom : 1) ); + + GetFont().SetSize(aFntSize); +} + +void SmMathSymbolNode::AdaptToY(OutputDevice &rDev, sal_uLong nHeight) +{ + GetFont().FreezeBorderWidth(); + Size aFntSize (GetFont().GetFontSize()); + + // Since we only want to scale the height, we might have + // to determine the font width in order to keep it + if (aFntSize.Width() == 0) + { + rDev.Push(vcl::PushFlags::FONT | vcl::PushFlags::MAPMODE); + rDev.SetFont(GetFont()); + aFntSize.setWidth( rDev.GetFontMetric().GetFontSize().Width() ); + rDev.Pop(); + } + OSL_ENSURE(aFntSize.Width() != 0, "Sm: "); + + //! however the result is a bit better with 'nHeight' as initial + //! font height + aFntSize.setHeight( nHeight ); + GetFont().SetSize(aFntSize); + + SmTmpDevice aTmpDev (rDev, true); + aTmpDev.SetFont(GetFont()); + + // get denominator of error factor for height + tools::Long nTmpBorderWidth = GetFont().GetBorderWidth(); + tools::Long nDenom = 0; + if (!GetText().isEmpty()) + nDenom = SmRect(aTmpDev, nullptr, GetText(), nTmpBorderWidth).GetHeight(); + + // scale fontwidth with this error factor + aFntSize.setHeight( aFntSize.Height() * nHeight ); + aFntSize.setHeight( aFntSize.Height() / ( nDenom ? nDenom : 1) ); + + GetFont().SetSize(aFntSize); +} + + +void SmMathSymbolNode::Prepare(const SmFormat &rFormat, const SmDocShell &rDocShell, int nDepth) +{ + SmNode::Prepare(rFormat, rDocShell, nDepth); + + GetFont() = rFormat.GetFont(GetFontDesc()); + // use same font size as is used for variables + GetFont().SetSize( rFormat.GetFont( FNT_VARIABLE ).GetFontSize() ); + + OSL_ENSURE(GetFont().GetCharSet() == RTL_TEXTENCODING_SYMBOL || + GetFont().GetCharSet() == RTL_TEXTENCODING_UNICODE, + "wrong charset for character from StarMath/OpenSymbol font"); + + Flags() |= FontChangeMask::Face | FontChangeMask::Italic; +}; + + +void SmMathSymbolNode::Arrange(OutputDevice &rDev, const SmFormat &rFormat) +{ + const OUString &rText = GetText(); + + if (rText.isEmpty() || rText[0] == '\0') + { SmRect::operator = (SmRect()); + return; + } + + PrepareAttributes(); + + GetFont() *= Fraction (rFormat.GetRelSize(SIZ_TEXT), 100); + + SmTmpDevice aTmpDev (rDev, true); + aTmpDev.SetFont(GetFont()); + + SmRect::operator = (SmRect(aTmpDev, &rFormat, rText, GetFont().GetBorderWidth())); +} + +/**************************************************************************/ + +SmSpecialNode::SmSpecialNode(SmNodeType eNodeType, const SmToken &rNodeToken, sal_uInt16 _nFontDesc) + : SmTextNode(eNodeType, rNodeToken, _nFontDesc) +{ +} + + +SmSpecialNode::SmSpecialNode(const SmToken &rNodeToken) + : SmTextNode(SmNodeType::Special, rNodeToken, FNT_VARIABLE) // default Font isn't always correct! +{ +} + + +void SmSpecialNode::Prepare(const SmFormat &rFormat, const SmDocShell &rDocShell, int nDepth) +{ + SmNode::Prepare(rFormat, rDocShell, nDepth); + + const SmSym *pSym; + SmModule *pp = SM_MOD(); + + bool bIsGreekSymbol = false; + bool bIsSpecialSymbol = false; + bool bIsArabic = false; + + if (nullptr != (pSym = pp->GetSymbolManager().GetSymbolByName(GetToken().aText.subView(1)))) + { + sal_UCS4 cChar = pSym->GetCharacter(); + OUString aTmp( &cChar, 1 ); + SetText( aTmp ); + GetFont() = pSym->GetFace(&rFormat); + + OUString aSymbolSetName = SmLocalizedSymbolData::GetExportSymbolSetName(pSym->GetSymbolSetName()); + if (aSymbolSetName == "Greek") + bIsGreekSymbol = true; + else if (aSymbolSetName == "Special") + bIsSpecialSymbol = true; + else if (aSymbolSetName == "Arabic") + bIsArabic = true; + } + else + { + SetText( GetToken().aText ); + GetFont() = rFormat.GetFont(FNT_VARIABLE); + } + // use same font size as is used for variables + GetFont().SetSize( rFormat.GetFont( FNT_VARIABLE ).GetFontSize() ); + + // Actually only WEIGHT_NORMAL and WEIGHT_BOLD should occur... However, the sms-file also + // contains e.g. 'WEIGHT_ULTRALIGHT'. Consequently, compare here with '>' instead of '!='. + // (In the long term the necessity for 'PrepareAttribut' and thus also for this here should be dropped) + + //! see also SmFontStyles::GetStyleName + if (IsItalic( GetFont() )) + SetAttribute(FontAttribute::Italic); + if (IsBold( GetFont() )) + SetAttribute(FontAttribute::Bold); + + Flags() |= FontChangeMask::Face; + + sal_uInt32 cChar = 0; + if (!GetText().isEmpty()) + { + sal_Int32 nIndex = 0; + cChar = GetText().iterateCodePoints(&nIndex); + if (!bIsArabic) + bIsArabic = u_getIntPropertyValue(cChar, UCHAR_SCRIPT) == USCRIPT_ARABIC; + } + + if (!bIsGreekSymbol && !bIsSpecialSymbol && !bIsArabic) + return; + + // Arabic and special symbols should not be italic, + // Greek is italic only in some cases. + bool bItalic = false; + if (bIsGreekSymbol) + { + sal_Int16 nStyle = rFormat.GetGreekCharStyle(); + OSL_ENSURE( nStyle >= 0 && nStyle <= 2, "unexpected value for GreekCharStyle" ); + if (nStyle == 1) + bItalic = true; + else if (nStyle == 2) + { + static const sal_Unicode cUppercaseAlpha = 0x0391; + static const sal_Unicode cUppercaseOmega = 0x03A9; + // uppercase letters should be straight and lowercase letters italic + bItalic = cUppercaseAlpha > cChar || cChar > cUppercaseOmega; + } + } + + if (bItalic) + Attributes() |= FontAttribute::Italic; + else + Attributes() &= ~FontAttribute::Italic; +}; + + +void SmSpecialNode::Arrange(OutputDevice &rDev, const SmFormat &rFormat) +{ + PrepareAttributes(); + + SmTmpDevice aTmpDev (rDev, true); + aTmpDev.SetFont(GetFont()); + + SmRect::operator = (SmRect(aTmpDev, &rFormat, GetText(), GetFont().GetBorderWidth())); +} + +/**************************************************************************/ + + +void SmGlyphSpecialNode::Arrange(OutputDevice &rDev, const SmFormat &rFormat) +{ + PrepareAttributes(); + + SmTmpDevice aTmpDev (rDev, true); + aTmpDev.SetFont(GetFont()); + + SmRect::operator = (SmRect(aTmpDev, &rFormat, GetText(), + GetFont().GetBorderWidth()).AsGlyphRect()); +} + + +/**************************************************************************/ + + +void SmPlaceNode::Prepare(const SmFormat &rFormat, const SmDocShell &rDocShell, int nDepth) +{ + SmNode::Prepare(rFormat, rDocShell, nDepth); + + GetFont().SetColor(COL_GRAY); + Flags() |= FontChangeMask::Color | FontChangeMask::Face | FontChangeMask::Italic; +}; + + +void SmPlaceNode::Arrange(OutputDevice &rDev, const SmFormat &rFormat) +{ + PrepareAttributes(); + + SmTmpDevice aTmpDev (rDev, true); + aTmpDev.SetFont(GetFont()); + + SmRect::operator = (SmRect(aTmpDev, &rFormat, GetText(), GetFont().GetBorderWidth())); +} + + +/**************************************************************************/ + + +void SmErrorNode::Prepare(const SmFormat &rFormat, const SmDocShell &rDocShell, int nDepth) +{ + SmNode::Prepare(rFormat, rDocShell, nDepth); + + GetFont().SetColor(COL_RED); + Flags() |= FontChangeMask::Phantom | FontChangeMask::Bold | FontChangeMask::Italic + | FontChangeMask::Color | FontChangeMask::Face | FontChangeMask::Size; +} + + +void SmErrorNode::Arrange(OutputDevice &rDev, const SmFormat &rFormat) +{ + PrepareAttributes(); + + SmTmpDevice aTmpDev (rDev, true); + aTmpDev.SetFont(GetFont()); + + const OUString &rText = GetText(); + SmRect::operator = (SmRect(aTmpDev, &rFormat, rText, GetFont().GetBorderWidth())); +} + +/**************************************************************************/ + +void SmBlankNode::IncreaseBy(const SmToken &rToken, sal_uInt32 nMultiplyBy) +{ + switch(rToken.eType) + { + case TBLANK: mnNum += (4 * nMultiplyBy); break; + case TSBLANK: mnNum += (1 * nMultiplyBy); break; + default: + break; + } +} + +void SmBlankNode::Prepare(const SmFormat &rFormat, const SmDocShell &rDocShell, int nDepth) +{ + SmNode::Prepare(rFormat, rDocShell, nDepth); + + // Here it need/should not be the StarMath font, so that for the character + // used in Arrange a normal (non-clipped) rectangle is generated + GetFont() = rFormat.GetFont(FNT_VARIABLE); + + Flags() |= FontChangeMask::Face | FontChangeMask::Bold | FontChangeMask::Italic; +} + + +void SmBlankNode::Arrange(OutputDevice &rDev, const SmFormat &rFormat) +{ + SmTmpDevice aTmpDev (rDev, true); + aTmpDev.SetFont(GetFont()); + + // make distance depend on the font height + // (so that it increases when scaling (e.g. size *2 {a ~ b}) + tools::Long nDist = GetFont().GetFontSize().Height() / 10, + nSpace = mnNum * nDist; + + // get a SmRect with Baseline and all the bells and whistles + SmRect::operator = (SmRect(aTmpDev, &rFormat, OUString(' '), + GetFont().GetBorderWidth())); + + // and resize it to the requested size + SetItalicSpaces(0, 0); + SetWidth(nSpace); +} + +/**************************************************************************/ +//Implementation of all accept methods for SmVisitor + +void SmTableNode::Accept(SmVisitor* pVisitor) { + pVisitor->Visit(this); +} + +void SmBraceNode::Accept(SmVisitor* pVisitor) { + pVisitor->Visit(this); +} + +void SmBracebodyNode::Accept(SmVisitor* pVisitor) { + pVisitor->Visit(this); +} + +void SmOperNode::Accept(SmVisitor* pVisitor) { + pVisitor->Visit(this); +} + +void SmAlignNode::Accept(SmVisitor* pVisitor) { + pVisitor->Visit(this); +} + +void SmAttributeNode::Accept(SmVisitor* pVisitor) { + pVisitor->Visit(this); +} + +void SmFontNode::Accept(SmVisitor* pVisitor) { + pVisitor->Visit(this); +} + +void SmUnHorNode::Accept(SmVisitor* pVisitor) { + pVisitor->Visit(this); +} + +void SmBinHorNode::Accept(SmVisitor* pVisitor) { + pVisitor->Visit(this); +} + +void SmBinVerNode::Accept(SmVisitor* pVisitor) { + pVisitor->Visit(this); +} + +void SmBinDiagonalNode::Accept(SmVisitor* pVisitor) { + pVisitor->Visit(this); +} + +void SmSubSupNode::Accept(SmVisitor* pVisitor) { + pVisitor->Visit(this); +} + +void SmMatrixNode::Accept(SmVisitor* pVisitor) { + pVisitor->Visit(this); +} + +void SmPlaceNode::Accept(SmVisitor* pVisitor) { + pVisitor->Visit(this); +} + +void SmTextNode::Accept(SmVisitor* pVisitor) { + pVisitor->Visit(this); +} + +void SmSpecialNode::Accept(SmVisitor* pVisitor) { + pVisitor->Visit(this); +} + +void SmGlyphSpecialNode::Accept(SmVisitor* pVisitor) { + pVisitor->Visit(this); +} + +void SmMathSymbolNode::Accept(SmVisitor* pVisitor) { + pVisitor->Visit(this); +} + +void SmBlankNode::Accept(SmVisitor* pVisitor) { + pVisitor->Visit(this); +} + +void SmErrorNode::Accept(SmVisitor* pVisitor) { + pVisitor->Visit(this); +} + +void SmLineNode::Accept(SmVisitor* pVisitor) { + pVisitor->Visit(this); +} + +void SmExpressionNode::Accept(SmVisitor* pVisitor) { + pVisitor->Visit(this); +} + +void SmPolyLineNode::Accept(SmVisitor* pVisitor) { + pVisitor->Visit(this); +} + +void SmRootNode::Accept(SmVisitor* pVisitor) { + pVisitor->Visit(this); +} + +void SmRootSymbolNode::Accept(SmVisitor* pVisitor) { + pVisitor->Visit(this); +} + +void SmRectangleNode::Accept(SmVisitor* pVisitor) { + pVisitor->Visit(this); +} + +void SmVerticalBraceNode::Accept(SmVisitor* pVisitor) { + pVisitor->Visit(this); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/ooxmlexport.cxx b/starmath/source/ooxmlexport.cxx new file mode 100644 index 0000000000..2956dd9a81 --- /dev/null +++ b/starmath/source/ooxmlexport.cxx @@ -0,0 +1,596 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include "ooxmlexport.hxx" + +#include <oox/token/tokens.hxx> +#include <rtl/ustring.hxx> +#include <sal/log.hxx> +#include <oox/mathml/imexport.hxx> + +using namespace oox; +using namespace oox::core; + +SmOoxmlExport::SmOoxmlExport(const SmNode *const pIn, OoxmlVersion const v, + drawingml::DocumentType const documentType) +: SmWordExportBase( pIn ) +, version( v ) +, m_DocumentType(documentType) +{ +} + +void SmOoxmlExport::ConvertFromStarMath( const ::sax_fastparser::FSHelperPtr& serializer, const sal_Int8 nAlign ) +{ + if( GetTree() == nullptr ) + return; + m_pSerializer = serializer; + + //Formula alignment situations: + // + // 1)Inline(as before): + // + // <m:oMath> + // <m:r> ... </m:r> + // </m:oMath> + // + // 2)Aligned: + // + // <m:oMathPara> + // <m:oMathParaPr> + // <m:jc m:val="left|right|center"> + // </m:oMathParaPr> + // <m:oMath> + // <m:r> ... </m:r> + // </m:oMath> + // </m:oMathPara> + + if (nAlign != FormulaImExportBase::eFormulaAlign::INLINE) + { + m_pSerializer->startElementNS(XML_m, XML_oMathPara, + FSNS(XML_xmlns, XML_m), "http://schemas.openxmlformats.org/officeDocument/2006/math"); + m_pSerializer->startElementNS(XML_m, XML_oMathParaPr); + if (nAlign == FormulaImExportBase::eFormulaAlign::CENTER) + m_pSerializer->singleElementNS(XML_m, XML_jc, FSNS(XML_m, XML_val), "center"); + if (nAlign == FormulaImExportBase::eFormulaAlign::LEFT) + m_pSerializer->singleElementNS(XML_m, XML_jc, FSNS(XML_m, XML_val), "left"); + if (nAlign == FormulaImExportBase::eFormulaAlign::RIGHT) + m_pSerializer->singleElementNS(XML_m, XML_jc, FSNS(XML_m, XML_val), "right"); + m_pSerializer->endElementNS(XML_m, XML_oMathParaPr); + m_pSerializer->startElementNS(XML_m, XML_oMath); + HandleNode(GetTree(), 0); + m_pSerializer->endElementNS(XML_m, XML_oMath); + m_pSerializer->endElementNS(XML_m, XML_oMathPara); + } + else //else, inline as was before + { + m_pSerializer->startElementNS(XML_m, XML_oMath, + FSNS(XML_xmlns, XML_m), "http://schemas.openxmlformats.org/officeDocument/2006/math"); + HandleNode( GetTree(), 0 ); + m_pSerializer->endElementNS( XML_m, XML_oMath ); + } +} + +// NOTE: This is still work in progress and unfinished, but it already covers a good +// part of the ooxml math stuff. + +void SmOoxmlExport::HandleVerticalStack( const SmNode* pNode, int nLevel ) +{ + m_pSerializer->startElementNS(XML_m, XML_eqArr); + int size = pNode->GetNumSubNodes(); + for( int i = 0; + i < size; + ++i ) + { + m_pSerializer->startElementNS(XML_m, XML_e); + HandleNode( pNode->GetSubNode( i ), nLevel + 1 ); + m_pSerializer->endElementNS( XML_m, XML_e ); + } + m_pSerializer->endElementNS( XML_m, XML_eqArr ); +} + +void SmOoxmlExport::HandleText( const SmNode* pNode, int /*nLevel*/) +{ + m_pSerializer->startElementNS(XML_m, XML_r); + + if( pNode->GetToken().eType == TTEXT ) // literal text (in quotes) + { + m_pSerializer->startElementNS(XML_m, XML_rPr); + m_pSerializer->singleElementNS(XML_m, XML_lit); + m_pSerializer->singleElementNS(XML_m, XML_nor); + m_pSerializer->endElementNS( XML_m, XML_rPr ); + } + if (drawingml::DOCUMENT_DOCX == m_DocumentType && ECMA_376_1ST_EDITION == version) + { // HACK: MSOffice2007 does not import characters properly unless this font is explicitly given + m_pSerializer->startElementNS(XML_w, XML_rPr); + m_pSerializer->singleElementNS( XML_w, XML_rFonts, FSNS( XML_w, XML_ascii ), "Cambria Math", + FSNS( XML_w, XML_hAnsi ), "Cambria Math" ); + m_pSerializer->endElementNS( XML_w, XML_rPr ); + } + m_pSerializer->startElementNS(XML_m, XML_t, FSNS(XML_xml, XML_space), "preserve"); + const SmTextNode* pTemp = static_cast<const SmTextNode* >(pNode); + SAL_INFO( "starmath.ooxml", "Text:" << pTemp->GetText()); + OUStringBuffer buf(pTemp->GetText()); + for(sal_Int32 i=0;i<pTemp->GetText().getLength();i++) + { +#if 0 + if ((nPendingAttributes) && + (i == ((pTemp->GetText().getLength()+1)/2)-1)) + { + *pS << sal_uInt8(0x22); //char, with attributes right + //after the character + } + else + *pS << sal_uInt8(CHAR); + + sal_uInt8 nFace = 0x1; + if (pNode->GetFont().GetItalic() == ITALIC_NORMAL) + nFace = 0x3; + else if (pNode->GetFont().GetWeight() == WEIGHT_BOLD) + nFace = 0x7; + *pS << sal_uInt8(nFace+128); //typeface +#endif + buf[i] = SmTextNode::ConvertSymbolToUnicode(buf[i]); +#if 0 + //Mathtype can only have these sort of character + //attributes on a single character, starmath can put them + //anywhere, when the entity involved is a text run this is + //a large effort to place the character attribute on the + //central mathtype character so that it does pretty much + //what the user probably has in mind. The attributes + //filled in here are dummy ones which are replaced in the + //ATTRIBUTE handler if a suitable location for the + //attributes was found here. Unfortunately it is + //possible for starmath to place character attributes on + //entities which cannot occur in mathtype e.g. a Summation + //symbol so these attributes may be lost + if ((nPendingAttributes) && + (i == ((pTemp->GetText().getLength()+1)/2)-1)) + { + *pS << sal_uInt8(EMBEL); + while (nPendingAttributes) + { + *pS << sal_uInt8(2); + //wedge the attributes in here and clear + //the pending stack + nPendingAttributes--; + } + nInsertion=pS->Tell(); + *pS << sal_uInt8(END); //end embel + *pS << sal_uInt8(END); //end embel + } +#endif + } + m_pSerializer->writeEscaped(buf); + m_pSerializer->endElementNS( XML_m, XML_t ); + m_pSerializer->endElementNS( XML_m, XML_r ); +} + +void SmOoxmlExport::HandleFractions( const SmNode* pNode, int nLevel, const char* type ) +{ + m_pSerializer->startElementNS(XML_m, XML_f); + if( type != nullptr ) + { + m_pSerializer->startElementNS(XML_m, XML_fPr); + m_pSerializer->singleElementNS(XML_m, XML_type, FSNS(XML_m, XML_val), type); + m_pSerializer->endElementNS( XML_m, XML_fPr ); + } + assert( pNode->GetNumSubNodes() == 3 ); + m_pSerializer->startElementNS(XML_m, XML_num); + HandleNode( pNode->GetSubNode( 0 ), nLevel + 1 ); + m_pSerializer->endElementNS( XML_m, XML_num ); + m_pSerializer->startElementNS(XML_m, XML_den); + HandleNode( pNode->GetSubNode( 2 ), nLevel + 1 ); + m_pSerializer->endElementNS( XML_m, XML_den ); + m_pSerializer->endElementNS( XML_m, XML_f ); +} + +void SmOoxmlExport::HandleAttribute( const SmAttributeNode* pNode, int nLevel ) +{ + switch( pNode->Attribute()->GetToken().eType ) + { + case TCHECK: + case TACUTE: + case TGRAVE: + case TBREVE: + case TCIRCLE: + case TVEC: + case TTILDE: + case THAT: + case TDOT: + case TDDOT: + case TDDDOT: + case TWIDETILDE: + case TWIDEHAT: + case TWIDEHARPOON: + case TWIDEVEC: + case TBAR: + { + m_pSerializer->startElementNS(XML_m, XML_acc); + m_pSerializer->startElementNS(XML_m, XML_accPr); + OString value = OUStringToOString(pNode->Attribute()->GetToken().cMathChar, RTL_TEXTENCODING_UTF8 ); + m_pSerializer->singleElementNS(XML_m, XML_chr, FSNS(XML_m, XML_val), value); + m_pSerializer->endElementNS( XML_m, XML_accPr ); + m_pSerializer->startElementNS(XML_m, XML_e); + HandleNode( pNode->Body(), nLevel + 1 ); + m_pSerializer->endElementNS( XML_m, XML_e ); + m_pSerializer->endElementNS( XML_m, XML_acc ); + break; + } + case TOVERLINE: + case TUNDERLINE: + m_pSerializer->startElementNS(XML_m, XML_bar); + m_pSerializer->startElementNS(XML_m, XML_barPr); + m_pSerializer->singleElementNS( XML_m, XML_pos, FSNS( XML_m, XML_val ), + ( pNode->Attribute()->GetToken().eType == TUNDERLINE ) ? "bot" : "top" ); + m_pSerializer->endElementNS( XML_m, XML_barPr ); + m_pSerializer->startElementNS(XML_m, XML_e); + HandleNode( pNode->Body(), nLevel + 1 ); + m_pSerializer->endElementNS( XML_m, XML_e ); + m_pSerializer->endElementNS( XML_m, XML_bar ); + break; + case TOVERSTRIKE: + m_pSerializer->startElementNS(XML_m, XML_borderBox); + m_pSerializer->startElementNS(XML_m, XML_borderBoxPr); + m_pSerializer->singleElementNS(XML_m, XML_hideTop, FSNS(XML_m, XML_val), "1"); + m_pSerializer->singleElementNS(XML_m, XML_hideBot, FSNS(XML_m, XML_val), "1"); + m_pSerializer->singleElementNS(XML_m, XML_hideLeft, FSNS(XML_m, XML_val), "1"); + m_pSerializer->singleElementNS(XML_m, XML_hideRight, FSNS(XML_m, XML_val), "1"); + m_pSerializer->singleElementNS(XML_m, XML_strikeH, FSNS(XML_m, XML_val), "1"); + m_pSerializer->endElementNS( XML_m, XML_borderBoxPr ); + m_pSerializer->startElementNS(XML_m, XML_e); + HandleNode( pNode->Body(), nLevel + 1 ); + m_pSerializer->endElementNS( XML_m, XML_e ); + m_pSerializer->endElementNS( XML_m, XML_borderBox ); + break; + default: + HandleAllSubNodes( pNode, nLevel ); + break; + } +} + +void SmOoxmlExport::HandleRoot( const SmRootNode* pNode, int nLevel ) +{ + m_pSerializer->startElementNS(XML_m, XML_rad); + if( const SmNode* argument = pNode->Argument()) + { + m_pSerializer->startElementNS(XML_m, XML_deg); + HandleNode( argument, nLevel + 1 ); + m_pSerializer->endElementNS( XML_m, XML_deg ); + } + else + { + m_pSerializer->startElementNS(XML_m, XML_radPr); + m_pSerializer->singleElementNS(XML_m, XML_degHide, FSNS(XML_m, XML_val), "1"); + m_pSerializer->endElementNS( XML_m, XML_radPr ); + m_pSerializer->singleElementNS(XML_m, XML_deg); // empty but present + } + m_pSerializer->startElementNS(XML_m, XML_e); + HandleNode( pNode->Body(), nLevel + 1 ); + m_pSerializer->endElementNS( XML_m, XML_e ); + m_pSerializer->endElementNS( XML_m, XML_rad ); +} + +static OString mathSymbolToString( const SmNode* node ) +{ + assert( node->GetType() == SmNodeType::Math || node->GetType() == SmNodeType::MathIdent ); + const SmTextNode* txtnode = static_cast< const SmTextNode* >( node ); + assert( txtnode->GetText().getLength() == 1 ); + sal_Unicode chr = SmTextNode::ConvertSymbolToUnicode( txtnode->GetText()[0] ); + return OUStringToOString( OUStringChar( chr ), RTL_TEXTENCODING_UTF8 ); +} + +void SmOoxmlExport::HandleOperator( const SmOperNode* pNode, int nLevel ) +{ + SAL_INFO( "starmath.ooxml", "Operator: " << int( pNode->GetToken().eType )); + switch( pNode->GetToken().eType ) + { + case TINT: + case TINTD: + case TIINT: + case TIIINT: + case TLINT: + case TLLINT: + case TLLLINT: + case TPROD: + case TCOPROD: + case TSUM: + { + const SmSubSupNode* subsup = pNode->GetSubNode( 0 )->GetType() == SmNodeType::SubSup + ? static_cast< const SmSubSupNode* >( pNode->GetSubNode( 0 )) : nullptr; + const SmNode* operation = subsup != nullptr ? subsup->GetBody() : pNode->GetSubNode( 0 ); + m_pSerializer->startElementNS(XML_m, XML_nary); + m_pSerializer->startElementNS(XML_m, XML_naryPr); + m_pSerializer->singleElementNS( XML_m, XML_chr, + FSNS( XML_m, XML_val ), mathSymbolToString(operation) ); + if( subsup == nullptr || subsup->GetSubSup( CSUB ) == nullptr ) + m_pSerializer->singleElementNS(XML_m, XML_subHide, FSNS(XML_m, XML_val), "1"); + if( subsup == nullptr || subsup->GetSubSup( CSUP ) == nullptr ) + m_pSerializer->singleElementNS(XML_m, XML_supHide, FSNS(XML_m, XML_val), "1"); + m_pSerializer->endElementNS( XML_m, XML_naryPr ); + if( subsup == nullptr || subsup->GetSubSup( CSUB ) == nullptr ) + m_pSerializer->singleElementNS(XML_m, XML_sub); + else + { + m_pSerializer->startElementNS(XML_m, XML_sub); + HandleNode( subsup->GetSubSup( CSUB ), nLevel + 1 ); + m_pSerializer->endElementNS( XML_m, XML_sub ); + } + if( subsup == nullptr || subsup->GetSubSup( CSUP ) == nullptr ) + m_pSerializer->singleElementNS(XML_m, XML_sup); + else + { + m_pSerializer->startElementNS(XML_m, XML_sup); + HandleNode( subsup->GetSubSup( CSUP ), nLevel + 1 ); + m_pSerializer->endElementNS( XML_m, XML_sup ); + } + m_pSerializer->startElementNS(XML_m, XML_e); + HandleNode( pNode->GetSubNode( 1 ), nLevel + 1 ); // body + m_pSerializer->endElementNS( XML_m, XML_e ); + m_pSerializer->endElementNS( XML_m, XML_nary ); + break; + } + case TLIM: + m_pSerializer->startElementNS(XML_m, XML_func); + m_pSerializer->startElementNS(XML_m, XML_fName); + m_pSerializer->startElementNS(XML_m, XML_limLow); + m_pSerializer->startElementNS(XML_m, XML_e); + HandleNode( pNode->GetSymbol(), nLevel + 1 ); + m_pSerializer->endElementNS( XML_m, XML_e ); + m_pSerializer->startElementNS(XML_m, XML_lim); + if( const SmSubSupNode* subsup = pNode->GetSubNode( 0 )->GetType() == SmNodeType::SubSup + ? static_cast< const SmSubSupNode* >( pNode->GetSubNode( 0 )) : nullptr ) + { + if( subsup->GetSubSup( CSUB ) != nullptr ) + HandleNode( subsup->GetSubSup( CSUB ), nLevel + 1 ); + } + m_pSerializer->endElementNS( XML_m, XML_lim ); + m_pSerializer->endElementNS( XML_m, XML_limLow ); + m_pSerializer->endElementNS( XML_m, XML_fName ); + m_pSerializer->startElementNS(XML_m, XML_e); + HandleNode( pNode->GetSubNode( 1 ), nLevel + 1 ); // body + m_pSerializer->endElementNS( XML_m, XML_e ); + m_pSerializer->endElementNS( XML_m, XML_func ); + break; + default: + SAL_WARN("starmath.ooxml", "Unhandled operation"); + HandleAllSubNodes( pNode, nLevel ); + break; + } +} + +void SmOoxmlExport::HandleSubSupScriptInternal( const SmSubSupNode* pNode, int nLevel, int flags ) +{ +// docx supports only a certain combination of sub/super scripts, but LO can have any, +// so try to merge it using several tags if necessary + if( flags == 0 ) // none + return; + if(( flags & ( 1 << RSUP | 1 << RSUB )) == ( 1 << RSUP | 1 << RSUB )) + { // m:sSubSup + m_pSerializer->startElementNS(XML_m, XML_sSubSup); + m_pSerializer->startElementNS(XML_m, XML_e); + flags &= ~( 1 << RSUP | 1 << RSUB ); + if( flags == 0 ) + HandleNode( pNode->GetBody(), nLevel + 1 ); + else + HandleSubSupScriptInternal( pNode, nLevel, flags ); + m_pSerializer->endElementNS( XML_m, XML_e ); + m_pSerializer->startElementNS(XML_m, XML_sub); + HandleNode( pNode->GetSubSup( RSUB ), nLevel + 1 ); + m_pSerializer->endElementNS( XML_m, XML_sub ); + m_pSerializer->startElementNS(XML_m, XML_sup); + HandleNode( pNode->GetSubSup( RSUP ), nLevel + 1 ); + m_pSerializer->endElementNS( XML_m, XML_sup ); + m_pSerializer->endElementNS( XML_m, XML_sSubSup ); + } + else if(( flags & ( 1 << RSUB )) == 1 << RSUB ) + { // m:sSub + m_pSerializer->startElementNS(XML_m, XML_sSub); + m_pSerializer->startElementNS(XML_m, XML_e); + flags &= ~( 1 << RSUB ); + if( flags == 0 ) + HandleNode( pNode->GetBody(), nLevel + 1 ); + else + HandleSubSupScriptInternal( pNode, nLevel, flags ); + m_pSerializer->endElementNS( XML_m, XML_e ); + m_pSerializer->startElementNS(XML_m, XML_sub); + HandleNode( pNode->GetSubSup( RSUB ), nLevel + 1 ); + m_pSerializer->endElementNS( XML_m, XML_sub ); + m_pSerializer->endElementNS( XML_m, XML_sSub ); + } + else if(( flags & ( 1 << RSUP )) == 1 << RSUP ) + { // m:sSup + m_pSerializer->startElementNS(XML_m, XML_sSup); + m_pSerializer->startElementNS(XML_m, XML_e); + flags &= ~( 1 << RSUP ); + if( flags == 0 ) + HandleNode( pNode->GetBody(), nLevel + 1 ); + else + HandleSubSupScriptInternal( pNode, nLevel, flags ); + m_pSerializer->endElementNS( XML_m, XML_e ); + m_pSerializer->startElementNS(XML_m, XML_sup); + HandleNode( pNode->GetSubSup( RSUP ), nLevel + 1 ); + m_pSerializer->endElementNS( XML_m, XML_sup ); + m_pSerializer->endElementNS( XML_m, XML_sSup ); + } + else if(( flags & ( 1 << LSUP | 1 << LSUB )) == ( 1 << LSUP | 1 << LSUB )) + { // m:sPre + m_pSerializer->startElementNS(XML_m, XML_sPre); + m_pSerializer->startElementNS(XML_m, XML_sub); + HandleNode( pNode->GetSubSup( LSUB ), nLevel + 1 ); + m_pSerializer->endElementNS( XML_m, XML_sub ); + m_pSerializer->startElementNS(XML_m, XML_sup); + HandleNode( pNode->GetSubSup( LSUP ), nLevel + 1 ); + m_pSerializer->endElementNS( XML_m, XML_sup ); + m_pSerializer->startElementNS(XML_m, XML_e); + flags &= ~( 1 << LSUP | 1 << LSUB ); + if( flags == 0 ) + HandleNode( pNode->GetBody(), nLevel + 1 ); + else + HandleSubSupScriptInternal( pNode, nLevel, flags ); + m_pSerializer->endElementNS( XML_m, XML_e ); + m_pSerializer->endElementNS( XML_m, XML_sPre ); + } + else if(( flags & ( 1 << CSUB )) == ( 1 << CSUB )) + { // m:limLow looks like a good element for central superscript + m_pSerializer->startElementNS(XML_m, XML_limLow); + m_pSerializer->startElementNS(XML_m, XML_e); + flags &= ~( 1 << CSUB ); + if( flags == 0 ) + HandleNode( pNode->GetBody(), nLevel + 1 ); + else + HandleSubSupScriptInternal( pNode, nLevel, flags ); + m_pSerializer->endElementNS( XML_m, XML_e ); + m_pSerializer->startElementNS(XML_m, XML_lim); + HandleNode( pNode->GetSubSup( CSUB ), nLevel + 1 ); + m_pSerializer->endElementNS( XML_m, XML_lim ); + m_pSerializer->endElementNS( XML_m, XML_limLow ); + } + else if(( flags & ( 1 << CSUP )) == ( 1 << CSUP )) + { // m:limUpp looks like a good element for central superscript + m_pSerializer->startElementNS(XML_m, XML_limUpp); + m_pSerializer->startElementNS(XML_m, XML_e); + flags &= ~( 1 << CSUP ); + if( flags == 0 ) + HandleNode( pNode->GetBody(), nLevel + 1 ); + else + HandleSubSupScriptInternal( pNode, nLevel, flags ); + m_pSerializer->endElementNS( XML_m, XML_e ); + m_pSerializer->startElementNS(XML_m, XML_lim); + HandleNode( pNode->GetSubSup( CSUP ), nLevel + 1 ); + m_pSerializer->endElementNS( XML_m, XML_lim ); + m_pSerializer->endElementNS( XML_m, XML_limUpp ); + } + else + { + SAL_WARN("starmath.ooxml", "Unhandled sub/sup combination"); + // TODO do not do anything, this should be probably an assert() + // HandleAllSubNodes( pNode, nLevel ); + } +} + +void SmOoxmlExport::HandleMatrix( const SmMatrixNode* pNode, int nLevel ) +{ + m_pSerializer->startElementNS(XML_m, XML_m); + for (size_t row = 0; row < pNode->GetNumRows(); ++row) + { + m_pSerializer->startElementNS(XML_m, XML_mr); + for (size_t col = 0; col < pNode->GetNumCols(); ++col) + { + m_pSerializer->startElementNS(XML_m, XML_e); + if( const SmNode* node = pNode->GetSubNode( row * pNode->GetNumCols() + col )) + HandleNode( node, nLevel + 1 ); + m_pSerializer->endElementNS( XML_m, XML_e ); + } + m_pSerializer->endElementNS( XML_m, XML_mr ); + } + m_pSerializer->endElementNS( XML_m, XML_m ); +} + +void SmOoxmlExport::HandleBrace( const SmBraceNode* pNode, int nLevel ) +{ + m_pSerializer->startElementNS(XML_m, XML_d); + m_pSerializer->startElementNS(XML_m, XML_dPr); + + //check if the node has an opening brace + if( TNONE == pNode->OpeningBrace()->GetToken().eType ) + m_pSerializer->singleElementNS(XML_m, XML_begChr, FSNS(XML_m, XML_val), ""); + else + m_pSerializer->singleElementNS( XML_m, XML_begChr, + FSNS( XML_m, XML_val ), mathSymbolToString( pNode->OpeningBrace()) ); + + std::vector< const SmNode* > subnodes; + if( pNode->Body()->GetType() == SmNodeType::Bracebody ) + { + const SmBracebodyNode* body = static_cast< const SmBracebodyNode* >( pNode->Body()); + bool separatorWritten = false; // assume all separators are the same + for (size_t i = 0; i < body->GetNumSubNodes(); ++i) + { + const SmNode* subnode = body->GetSubNode( i ); + if (subnode->GetType() == SmNodeType::Math || subnode->GetType() == SmNodeType::MathIdent) + { // do not write, but write what separator it is + const SmMathSymbolNode* math = static_cast< const SmMathSymbolNode* >( subnode ); + if( !separatorWritten ) + { + m_pSerializer->singleElementNS( XML_m, XML_sepChr, + FSNS( XML_m, XML_val ), mathSymbolToString(math) ); + separatorWritten = true; + } + } + else + subnodes.push_back( subnode ); + } + } + else + subnodes.push_back( pNode->Body()); + + if( TNONE == pNode->ClosingBrace()->GetToken().eType ) + m_pSerializer->singleElementNS(XML_m, XML_endChr, FSNS(XML_m, XML_val), ""); + else + m_pSerializer->singleElementNS( XML_m, XML_endChr, + FSNS( XML_m, XML_val ), mathSymbolToString(pNode->ClosingBrace()) ); + + m_pSerializer->endElementNS( XML_m, XML_dPr ); + for(const SmNode* subnode : subnodes) + { + m_pSerializer->startElementNS(XML_m, XML_e); + HandleNode( subnode, nLevel + 1 ); + m_pSerializer->endElementNS( XML_m, XML_e ); + } + m_pSerializer->endElementNS( XML_m, XML_d ); +} + +void SmOoxmlExport::HandleVerticalBrace( const SmVerticalBraceNode* pNode, int nLevel ) +{ + SAL_INFO( "starmath.ooxml", "Vertical: " << int( pNode->GetToken().eType )); + switch( pNode->GetToken().eType ) + { + case TOVERBRACE: + case TUNDERBRACE: + { + bool top = ( pNode->GetToken().eType == TOVERBRACE ); + m_pSerializer->startElementNS(XML_m, top ? XML_limUpp : XML_limLow); + m_pSerializer->startElementNS(XML_m, XML_e); + m_pSerializer->startElementNS(XML_m, XML_groupChr); + m_pSerializer->startElementNS(XML_m, XML_groupChrPr); + m_pSerializer->singleElementNS( XML_m, XML_chr, + FSNS( XML_m, XML_val ), mathSymbolToString(pNode->Brace()) ); + // TODO not sure if pos and vertJc are correct + m_pSerializer->singleElementNS( XML_m, XML_pos, + FSNS( XML_m, XML_val ), top ? "top" : "bot" ); + m_pSerializer->singleElementNS(XML_m, XML_vertJc, FSNS(XML_m, XML_val), + top ? "bot" : "top"); + m_pSerializer->endElementNS( XML_m, XML_groupChrPr ); + m_pSerializer->startElementNS(XML_m, XML_e); + HandleNode( pNode->Body(), nLevel + 1 ); + m_pSerializer->endElementNS( XML_m, XML_e ); + m_pSerializer->endElementNS( XML_m, XML_groupChr ); + m_pSerializer->endElementNS( XML_m, XML_e ); + m_pSerializer->startElementNS(XML_m, XML_lim); + HandleNode( pNode->Script(), nLevel + 1 ); + m_pSerializer->endElementNS( XML_m, XML_lim ); + m_pSerializer->endElementNS( XML_m, top ? XML_limUpp : XML_limLow ); + break; + } + default: + SAL_WARN("starmath.ooxml", "Unhandled vertical brace"); + HandleAllSubNodes( pNode, nLevel ); + break; + } +} + +void SmOoxmlExport::HandleBlank() +{ + m_pSerializer->startElementNS(XML_m, XML_r); + m_pSerializer->startElementNS(XML_m, XML_t, FSNS(XML_xml, XML_space), "preserve"); + m_pSerializer->write( " " ); + m_pSerializer->endElementNS( XML_m, XML_t ); + m_pSerializer->endElementNS( XML_m, XML_r ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/ooxmlexport.hxx b/starmath/source/ooxmlexport.hxx new file mode 100644 index 0000000000..cd2e9d4465 --- /dev/null +++ b/starmath/source/ooxmlexport.hxx @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#pragma once + +#include "wordexportbase.hxx" + +#include <sax/fshelper.hxx> +#include <oox/core/filterbase.hxx> +#include <oox/export/utils.hxx> + +/** + Class implementing writing of formulas to OOXML. + */ +class SmOoxmlExport : public SmWordExportBase +{ +public: + SmOoxmlExport(const SmNode* pIn, oox::core::OoxmlVersion version, + oox::drawingml::DocumentType documentType); + void ConvertFromStarMath(const ::sax_fastparser::FSHelperPtr& m_pSerializer, const sal_Int8); + +private: + void HandleVerticalStack(const SmNode* pNode, int nLevel) override; + void HandleText(const SmNode* pNode, int nLevel) override; + void HandleFractions(const SmNode* pNode, int nLevel, const char* type) override; + void HandleRoot(const SmRootNode* pNode, int nLevel) override; + void HandleAttribute(const SmAttributeNode* pNode, int nLevel) override; + void HandleOperator(const SmOperNode* pNode, int nLevel) override; + void HandleSubSupScriptInternal(const SmSubSupNode* pNode, int nLevel, int flags) override; + void HandleMatrix(const SmMatrixNode* pNode, int nLevel) override; + void HandleBrace(const SmBraceNode* pNode, int nLevel) override; + void HandleVerticalBrace(const SmVerticalBraceNode* pNode, int nLevel) override; + void HandleBlank() override; + ::sax_fastparser::FSHelperPtr m_pSerializer; + oox::core::OoxmlVersion version; + /// needed to determine markup for nested run properties + oox::drawingml::DocumentType const m_DocumentType; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/ooxmlimport.cxx b/starmath/source/ooxmlimport.cxx new file mode 100644 index 0000000000..4023a5e652 --- /dev/null +++ b/starmath/source/ooxmlimport.cxx @@ -0,0 +1,685 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <sal/config.h> + +#include <string_view> + +#include "ooxmlimport.hxx" +#include <types.hxx> + +#include <oox/mathml/importutils.hxx> +#include <oox/token/namespaces.hxx> +#include <rtl/ustring.hxx> +#include <rtl/ustrbuf.hxx> +#include <sal/log.hxx> +#include <o3tl/string_view.hxx> + +using namespace oox::formulaimport; + +/* +The primary internal data structure for the formula is the text representation +(the SmNode tree is built from it), so read data must be converted into this format. +*/ + +#define OPENING( token ) XML_STREAM_OPENING( token ) +#define CLOSING( token ) XML_STREAM_CLOSING( token ) + +// TODO create IS_OPENING(), IS_CLOSING() instead of doing 'next == OPENING( next )' ? + +SmOoxmlImport::SmOoxmlImport( oox::formulaimport::XmlStream& s ) + : m_rStream( s ) +{ +} + +OUString SmOoxmlImport::ConvertToStarMath() +{ + return handleStream(); +} + +// "toplevel" of reading, there will be oMath (if there was oMathPara, that was +// up to the parent component to handle) + +// NOT complete +OUString SmOoxmlImport::handleStream() +{ + m_rStream.ensureOpeningTag( M_TOKEN( oMath )); + OUStringBuffer ret; + while( !m_rStream.atEnd() && m_rStream.currentToken() != CLOSING( M_TOKEN( oMath ))) + { + // strictly speaking, it is not OMathArg here, but currently supported + // functionality is the same like OMathArg, in the future this may need improving + OUString item = readOMathArg( M_TOKEN( oMath )); + if( item.isEmpty()) + continue; + if( !ret.isEmpty()) + ret.append(" "); + ret.append(item); + } + m_rStream.ensureClosingTag( M_TOKEN( oMath )); + // Placeholders are written out as nothing (i.e. nothing inside e.g. the <e> element), + // which will result in "{}" in the formula text. Fix this up. + OUString ret2 = ret.makeStringAndClear().replaceAll( "{}", "<?>" ); + // And as a result, empty parts of the formula that are not placeholders are written out + // as a single space, so fix that up too. + ret2 = ret2.replaceAll( "{ }", "{}" ); + SAL_INFO( "starmath.ooxml", "Formula: " << ret2 ); + return ret2; +} + +OUString SmOoxmlImport::readOMathArg( int stoptoken ) +{ + OUStringBuffer ret; + while( !m_rStream.atEnd() && m_rStream.currentToken() != CLOSING( stoptoken )) + { + if( !ret.isEmpty()) + ret.append(" "); + switch( m_rStream.currentToken()) + { + case OPENING( M_TOKEN( acc )): + ret.append(handleAcc()); + break; + case OPENING( M_TOKEN( bar )): + ret.append(handleBar()); + break; + case OPENING( M_TOKEN( box )): + ret.append(handleBox()); + break; + case OPENING( M_TOKEN( borderBox )): + ret.append(handleBorderBox()); + break; + case OPENING( M_TOKEN( d )): + ret.append(handleD()); + break; + case OPENING( M_TOKEN( eqArr )): + ret.append(handleEqArr()); + break; + case OPENING( M_TOKEN( f )): + ret.append(handleF()); + break; + case OPENING( M_TOKEN( func )): + ret.append(handleFunc()); + break; + case OPENING( M_TOKEN( limLow )): + ret.append(handleLimLowUpp( LimLow )); + break; + case OPENING( M_TOKEN( limUpp )): + ret.append(handleLimLowUpp( LimUpp )); + break; + case OPENING( M_TOKEN( groupChr )): + ret.append(handleGroupChr()); + break; + case OPENING( M_TOKEN( m )): + ret.append(handleM()); + break; + case OPENING( M_TOKEN( nary )): + ret.append(handleNary()); + break; + case OPENING( M_TOKEN( r )): + ret.append(handleR()); + break; + case OPENING( M_TOKEN( rad )): + ret.append(handleRad()); + break; + case OPENING( M_TOKEN( sPre )): + ret.append(handleSpre()); + break; + case OPENING( M_TOKEN( sSub )): + ret.append(handleSsub()); + break; + case OPENING( M_TOKEN( sSubSup )): + ret.append(handleSsubsup()); + break; + case OPENING( M_TOKEN( sSup )): + ret.append(handleSsup()); + break; + default: + m_rStream.handleUnexpectedTag(); + break; + } + } + return ret.makeStringAndClear(); +} + +OUString SmOoxmlImport::readOMathArgInElement( int token ) +{ + m_rStream.ensureOpeningTag( token ); + OUString ret = readOMathArg( token ); + m_rStream.ensureClosingTag( token ); + return ret; +} + +OUString SmOoxmlImport::handleAcc() +{ + m_rStream.ensureOpeningTag( M_TOKEN( acc )); + sal_Unicode accChr = 0x302; + if( XmlStream::Tag accPr = m_rStream.checkOpeningTag( M_TOKEN( accPr ))) + { + if( XmlStream::Tag chr = m_rStream.checkOpeningTag( M_TOKEN( chr ))) + { + accChr = chr.attribute( M_TOKEN( val ), accChr ); + m_rStream.ensureClosingTag( M_TOKEN( chr )); + } + m_rStream.ensureClosingTag( M_TOKEN( accPr )); + } + // see aTokenTable in parse.cxx + OUString acc; + switch( accChr ) + { + case MS_BAR: + case MS_COMBBAR: + acc = "bar"; + break; + case MS_CHECK: + case MS_COMBCHECK: + acc = "check"; + break; + case MS_ACUTE: + case MS_COMBACUTE: + acc = "acute"; + break; + case MS_COMBOVERLINE: + acc = "overline"; + break; + case MS_GRAVE: + case MS_COMBGRAVE: + acc = "grave"; + break; + case MS_BREVE: + case MS_COMBBREVE: + acc = "breve"; + break; + case MS_CIRCLE: + case MS_COMBCIRCLE: + acc = "circle"; + break; + case MS_RIGHTARROW: + case MS_VEC: + // prefer wide variants for these 3, .docx can't seem to differentiate + // between e.g. 'vec' and 'widevec', if whatever the accent is above is short, this + // shouldn't matter, but short above a longer expression doesn't look right + acc = "widevec"; + break; + case MS_HARPOON: + acc = "wideharpoon"; + break; + case MS_TILDE: + case MS_COMBTILDE: + acc = "widetilde"; + break; + case MS_HAT: + case MS_COMBHAT: + acc = "widehat"; + break; + case MS_DOT: + case MS_COMBDOT: + acc = "dot"; + break; + case MS_DDOT: + case MS_COMBDDOT: + acc = "ddot"; + break; + case MS_DDDOT: + acc = "dddot"; + break; + default: + acc = "acute"; + SAL_WARN( "starmath.ooxml", "Unknown m:chr in m:acc \'" << OUString(accChr) << "\'" ); + break; + } + OUString e = readOMathArgInElement( M_TOKEN( e )); + m_rStream.ensureClosingTag( M_TOKEN( acc )); + return acc + " {" + e + "}"; +} + +OUString SmOoxmlImport::handleBar() +{ + m_rStream.ensureOpeningTag( M_TOKEN( bar )); + enum pos_t { top, bot } topbot = bot; + if( m_rStream.checkOpeningTag( M_TOKEN( barPr ))) + { + if( XmlStream::Tag pos = m_rStream.checkOpeningTag( M_TOKEN( pos ))) + { + if( pos.attribute( M_TOKEN( val )) == "top" ) + topbot = top; + else if( pos.attribute( M_TOKEN( val )) == "bot" ) + topbot = bot; + m_rStream.ensureClosingTag( M_TOKEN( pos )); + } + m_rStream.ensureClosingTag( M_TOKEN( barPr )); + } + OUString e = readOMathArgInElement( M_TOKEN( e )); + m_rStream.ensureClosingTag( M_TOKEN( bar )); + if( topbot == top ) + return "overline {" + e + "}"; + else + return "underline {" + e + "}"; +} + +OUString SmOoxmlImport::handleBox() +{ + // there does not seem to be functionality in LO to actually implement this + // (or is there), but at least read in the contents instead of ignoring them + m_rStream.ensureOpeningTag( M_TOKEN( box )); + OUString e = readOMathArgInElement( M_TOKEN( e )); + m_rStream.ensureClosingTag( M_TOKEN( box )); + return e; +} + + +OUString SmOoxmlImport::handleBorderBox() +{ + m_rStream.ensureOpeningTag( M_TOKEN( borderBox )); + bool isStrikeH = false; + if( m_rStream.checkOpeningTag( M_TOKEN( borderBoxPr ))) + { + if( XmlStream::Tag strikeH = m_rStream.checkOpeningTag( M_TOKEN( strikeH ))) + { + if( strikeH.attribute( M_TOKEN( val ), false )) + isStrikeH = true; + m_rStream.ensureClosingTag( M_TOKEN( strikeH )); + } + m_rStream.ensureClosingTag( M_TOKEN( borderBoxPr )); + } + OUString e = readOMathArgInElement( M_TOKEN( e )); + m_rStream.ensureClosingTag( M_TOKEN( borderBox )); + if( isStrikeH ) + return "overstrike {" + e + "}"; + // LO does not seem to implement anything for handling the other cases + return e; +} + +OUString SmOoxmlImport::handleD() +{ + m_rStream.ensureOpeningTag( M_TOKEN( d )); + OUString opening = "("; + OUString closing = ")"; + OUString separator = "|"; + if( XmlStream::Tag dPr = m_rStream.checkOpeningTag( M_TOKEN( dPr ))) + { + if( XmlStream::Tag begChr = m_rStream.checkOpeningTag( M_TOKEN( begChr ))) + { + opening = begChr.attribute( M_TOKEN( val ), opening ); + m_rStream.ensureClosingTag( M_TOKEN( begChr )); + } + if( XmlStream::Tag sepChr = m_rStream.checkOpeningTag( M_TOKEN( sepChr ))) + { + separator = sepChr.attribute( M_TOKEN( val ), separator ); + m_rStream.ensureClosingTag( M_TOKEN( sepChr )); + } + if( XmlStream::Tag endChr = m_rStream.checkOpeningTag( M_TOKEN( endChr ))) + { + closing = endChr.attribute( M_TOKEN( val ), closing ); + m_rStream.ensureClosingTag( M_TOKEN( endChr )); + } + m_rStream.ensureClosingTag( M_TOKEN( dPr )); + } + if( opening == "{" ) + opening = "left lbrace "; + if( closing == "}" ) + closing = " right rbrace"; + if( opening == u"\u27e6" ) + opening = "left ldbracket "; + if( closing == u"\u27e7" ) + closing = " right rdbracket"; + if( opening == "|" ) + opening = "left lline "; + if( closing == "|" ) + closing = " right rline"; + if (opening == OUStringChar(MS_DLINE) + || opening == OUStringChar(MS_DVERTLINE)) + opening = "left ldline "; + if (closing == OUStringChar(MS_DLINE) + || closing == OUStringChar(MS_DVERTLINE)) + closing = " right rdline"; + if (opening == OUStringChar(MS_LANGLE) + || opening == OUStringChar(MS_LMATHANGLE)) + opening = "left langle "; + if (closing == OUStringChar(MS_RANGLE) + || closing == OUStringChar(MS_RMATHANGLE)) + closing = " right rangle"; + // use scalable brackets (the explicit "left" or "right") + if( opening == "(" || opening == "[" ) + opening = "left " + opening; + if( closing == ")" || closing == "]" ) + closing = " right " + closing; + if( separator == "|" ) // plain "|" would be actually "V" (logical or) + separator = " mline "; + if( opening.isEmpty()) + opening = "left none "; + if( closing.isEmpty()) + closing = " right none"; + OUStringBuffer ret( opening ); + bool first = true; + while( m_rStream.findTag( OPENING( M_TOKEN( e )))) + { + if( !first ) + ret.append( separator ); + first = false; + ret.append( readOMathArgInElement( M_TOKEN( e ))); + } + ret.append( closing ); + m_rStream.ensureClosingTag( M_TOKEN( d )); + return ret.makeStringAndClear(); +} + +OUString SmOoxmlImport::handleEqArr() +{ + m_rStream.ensureOpeningTag( M_TOKEN( eqArr )); + OUStringBuffer ret; + do + { // there must be at least one m:e + if( !ret.isEmpty()) + ret.append("#"); + ret.append(" " + + readOMathArgInElement( M_TOKEN( e )) + + " "); + } while( !m_rStream.atEnd() && m_rStream.findTag( OPENING( M_TOKEN( e )))); + m_rStream.ensureClosingTag( M_TOKEN( eqArr )); + return "stack {" + ret + "}"; +} + +OUString SmOoxmlImport::handleF() +{ + m_rStream.ensureOpeningTag( M_TOKEN( f )); + enum operation_t { bar, lin, noBar } operation = bar; + if( m_rStream.checkOpeningTag( M_TOKEN( fPr ))) + { + if( XmlStream::Tag type = m_rStream.checkOpeningTag( M_TOKEN( type ))) + { + if( type.attribute( M_TOKEN( val )) == "bar" ) + operation = bar; + else if( type.attribute( M_TOKEN( val )) == "lin" ) + operation = lin; + else if( type.attribute( M_TOKEN( val )) == "noBar" ) + operation = noBar; + m_rStream.ensureClosingTag( M_TOKEN( type )); + } + m_rStream.ensureClosingTag( M_TOKEN( fPr )); + } + OUString num = readOMathArgInElement( M_TOKEN( num )); + OUString den = readOMathArgInElement( M_TOKEN( den )); + m_rStream.ensureClosingTag( M_TOKEN( f )); + if( operation == bar ) + return "{" + num + "} over {" + den + "}"; + else if( operation == lin ) + return "{" + num + "} / {" + den + "}"; + else // noBar + { + return "binom {" + num + "} {" + den + "}"; + } +} + +OUString SmOoxmlImport::handleFunc() +{ +//lim from{x rightarrow 1} x + m_rStream.ensureOpeningTag( M_TOKEN( func )); + OUString fname = readOMathArgInElement( M_TOKEN( fName )); + // fix the various functions + if( fname.startsWith( "lim csub {" )) + fname = OUString::Concat("lim from {") + fname.subView( 10 ); + OUString ret = fname + " {" + readOMathArgInElement( M_TOKEN( e )) + "}"; + m_rStream.ensureClosingTag( M_TOKEN( func )); + return ret; +} + +OUString SmOoxmlImport::handleLimLowUpp( LimLowUpp_t limlowupp ) +{ + int token = limlowupp == LimLow ? M_TOKEN( limLow ) : M_TOKEN( limUpp ); + m_rStream.ensureOpeningTag( token ); + OUString e = readOMathArgInElement( M_TOKEN( e )); + OUString lim = readOMathArgInElement( M_TOKEN( lim )); + m_rStream.ensureClosingTag( token ); + // fix up overbrace/underbrace (use { }, as {} will be converted to a placeholder) + if( limlowupp == LimUpp && e.endsWith( " overbrace { }" )) + return e.subView( 0, e.getLength() - 2 ) + lim + "}"; + if( limlowupp == LimLow && e.endsWith( " underbrace { }" )) + return e.subView( 0, e.getLength() - 2 ) + lim + "}"; + return e + + ( limlowupp == LimLow + ? std::u16string_view( u" csub {" ) : std::u16string_view( u" csup {" )) + + lim + "}"; +} + +OUString SmOoxmlImport::handleGroupChr() +{ + m_rStream.ensureOpeningTag( M_TOKEN( groupChr )); + sal_Unicode chr = 0x23df; + enum pos_t { top, bot } pos = bot; + if( m_rStream.checkOpeningTag( M_TOKEN( groupChrPr ))) + { + if( XmlStream::Tag chrTag = m_rStream.checkOpeningTag( M_TOKEN( chr ))) + { + chr = chrTag.attribute( M_TOKEN( val ), chr ); + m_rStream.ensureClosingTag( M_TOKEN( chr )); + } + if( XmlStream::Tag posTag = m_rStream.checkOpeningTag( M_TOKEN( pos ))) + { + if( posTag.attribute( M_TOKEN( val ), OUString( "bot" )) == "top" ) + pos = top; + m_rStream.ensureClosingTag( M_TOKEN( pos )); + } + m_rStream.ensureClosingTag( M_TOKEN( groupChrPr )); + } + OUString e = readOMathArgInElement( M_TOKEN( e )); + m_rStream.ensureClosingTag( M_TOKEN( groupChr )); + if( pos == top && chr == u'\x23de') + return "{" + e + "} overbrace { }"; + if( pos == bot && chr == u'\x23df') + return "{" + e + "} underbrace { }"; + if( pos == top ) + return "{" + e + "} csup {" + OUStringChar( chr ) + "}"; + else + return "{" + e + "} csub {" + OUStringChar( chr ) + "}"; +} + +OUString SmOoxmlImport::handleM() +{ + m_rStream.ensureOpeningTag( M_TOKEN( m )); + OUStringBuffer allrows; + do // there must be at least one m:mr + { + m_rStream.ensureOpeningTag( M_TOKEN( mr )); + OUStringBuffer row; + do // there must be at least one m:e + { + if( !row.isEmpty()) + row.append(" # "); + row.append(readOMathArgInElement( M_TOKEN( e ))); + } while( !m_rStream.atEnd() && m_rStream.findTag( OPENING( M_TOKEN( e )))); + if( !allrows.isEmpty()) + allrows.append(" ## "); + allrows.append(row); + m_rStream.ensureClosingTag( M_TOKEN( mr )); + } while( !m_rStream.atEnd() && m_rStream.findTag( OPENING( M_TOKEN( mr )))); + m_rStream.ensureClosingTag( M_TOKEN( m )); + return "matrix {" + allrows + "}"; +} + +OUString SmOoxmlImport::handleNary() +{ + m_rStream.ensureOpeningTag( M_TOKEN( nary )); + sal_Unicode chr = 0x222b; + bool subHide = false; + bool supHide = false; + if( m_rStream.checkOpeningTag( M_TOKEN( naryPr ))) + { + if( XmlStream::Tag chrTag = m_rStream.checkOpeningTag( M_TOKEN( chr ))) + { + chr = chrTag.attribute( M_TOKEN( val ), chr ); + m_rStream.ensureClosingTag( M_TOKEN( chr )); + } + if( XmlStream::Tag subHideTag = m_rStream.checkOpeningTag( M_TOKEN( subHide ))) + { + subHide = subHideTag.attribute( M_TOKEN( val ), subHide ); + m_rStream.ensureClosingTag( M_TOKEN( subHide )); + } + if( XmlStream::Tag supHideTag = m_rStream.checkOpeningTag( M_TOKEN( supHide ))) + { + supHide = supHideTag.attribute( M_TOKEN( val ), supHide ); + m_rStream.ensureClosingTag( M_TOKEN( supHide )); + } + m_rStream.ensureClosingTag( M_TOKEN( naryPr )); + } + OUString sub = readOMathArgInElement( M_TOKEN( sub )); + OUString sup = readOMathArgInElement( M_TOKEN( sup )); + OUString e = readOMathArgInElement( M_TOKEN( e )); + OUString ret; + switch( chr ) + { + case MS_INT: + ret = "int"; + break; + case MS_IINT: + ret = "iint"; + break; + case MS_IIINT: + ret = "iiint"; + break; + case MS_LINT: + ret = "lint"; + break; + case MS_LLINT: + ret = "llint"; + break; + case MS_LLLINT: + ret = "lllint"; + break; + case MS_PROD: + ret = "prod"; + break; + case MS_COPROD: + ret = "coprod"; + break; + case MS_SUM: + ret = "sum"; + break; + default: + SAL_WARN( "starmath.ooxml", "Unknown m:nary chr \'" << OUString(chr) << "\'" ); + break; + } + if( !subHide ) + ret += " from {" + sub + "}"; + if( !supHide ) + ret += " to {" + sup + "}"; + ret += " {" + e + "}"; + m_rStream.ensureClosingTag( M_TOKEN( nary )); + return ret; +} + +// NOT complete +OUString SmOoxmlImport::handleR() +{ + m_rStream.ensureOpeningTag( M_TOKEN( r )); + bool normal = false; + bool literal = false; + if( XmlStream::Tag rPr = m_rStream.checkOpeningTag( M_TOKEN( rPr ))) + { + if( XmlStream::Tag litTag = m_rStream.checkOpeningTag( M_TOKEN( lit ))) + { + literal = litTag.attribute( M_TOKEN( val ), true ); + m_rStream.ensureClosingTag( M_TOKEN( lit )); + } + if( XmlStream::Tag norTag = m_rStream.checkOpeningTag( M_TOKEN( nor ))) + { + normal = norTag.attribute( M_TOKEN( val ), true ); + m_rStream.ensureClosingTag( M_TOKEN( nor )); + } + m_rStream.ensureClosingTag( M_TOKEN( rPr )); + } + OUStringBuffer text; + while( !m_rStream.atEnd() && m_rStream.currentToken() != CLOSING( m_rStream.currentToken())) + { + switch( m_rStream.currentToken()) + { + case OPENING( M_TOKEN( t )): + { + XmlStream::Tag rtag = m_rStream.ensureOpeningTag( M_TOKEN( t )); + if( rtag.attribute( OOX_TOKEN( xml, space )) != "preserve" ) + text.append(o3tl::trim(rtag.text)); + else + text.append(rtag.text); + m_rStream.ensureClosingTag( M_TOKEN( t )); + break; + } + default: + m_rStream.handleUnexpectedTag(); + break; + } + } + m_rStream.ensureClosingTag( M_TOKEN( r )); + if( normal || literal ) + { + text.insert(0, "\""); + text.append("\""); + } + return text.makeStringAndClear().replaceAll("{", "\\{").replaceAll("}", "\\}"); +} + +OUString SmOoxmlImport::handleRad() +{ + m_rStream.ensureOpeningTag( M_TOKEN( rad )); + bool degHide = false; + if( m_rStream.checkOpeningTag( M_TOKEN( radPr ))) + { + if( XmlStream::Tag degHideTag = m_rStream.checkOpeningTag( M_TOKEN( degHide ))) + { + degHide = degHideTag.attribute( M_TOKEN( val ), degHide ); + m_rStream.ensureClosingTag( M_TOKEN( degHide )); + } + m_rStream.ensureClosingTag( M_TOKEN( radPr )); + } + OUString deg = readOMathArgInElement( M_TOKEN( deg )); + OUString e = readOMathArgInElement( M_TOKEN( e )); + m_rStream.ensureClosingTag( M_TOKEN( rad )); + if( degHide ) + return "sqrt {" + e + "}"; + else + return "nroot {" + deg + "} {" + e + "}"; +} + +OUString SmOoxmlImport::handleSpre() +{ + m_rStream.ensureOpeningTag( M_TOKEN( sPre )); + OUString sub = readOMathArgInElement( M_TOKEN( sub )); + OUString sup = readOMathArgInElement( M_TOKEN( sup )); + OUString e = readOMathArgInElement( M_TOKEN( e )); + m_rStream.ensureClosingTag( M_TOKEN( sPre )); + return "{" + e + "} lsub {" + sub + "} lsup {" + sup + "}"; +} + +OUString SmOoxmlImport::handleSsub() +{ + m_rStream.ensureOpeningTag( M_TOKEN( sSub )); + OUString e = readOMathArgInElement( M_TOKEN( e )); + OUString sub = readOMathArgInElement( M_TOKEN( sub )); + m_rStream.ensureClosingTag( M_TOKEN( sSub )); + return "{" + e + "} rsub {" + sub + "}"; +} + +OUString SmOoxmlImport::handleSsubsup() +{ + m_rStream.ensureOpeningTag( M_TOKEN( sSubSup )); + OUString e = readOMathArgInElement( M_TOKEN( e )); + OUString sub = readOMathArgInElement( M_TOKEN( sub )); + OUString sup = readOMathArgInElement( M_TOKEN( sup )); + m_rStream.ensureClosingTag( M_TOKEN( sSubSup )); + return "{" + e + "} rsub {" + sub + "} rsup {" + sup + "}"; +} + +OUString SmOoxmlImport::handleSsup() +{ + m_rStream.ensureOpeningTag( M_TOKEN( sSup )); + OUString e = readOMathArgInElement( M_TOKEN( e )); + OUString sup = readOMathArgInElement( M_TOKEN( sup )); + m_rStream.ensureClosingTag( M_TOKEN( sSup )); + return "{" + e + "} ^ {" + sup + "}"; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/ooxmlimport.hxx b/starmath/source/ooxmlimport.hxx new file mode 100644 index 0000000000..4f8df14bfd --- /dev/null +++ b/starmath/source/ooxmlimport.hxx @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#pragma once + +#include <rtl/ustring.hxx> + +namespace oox::formulaimport { class XmlStream; } +/** + Class implementing reading of formulas from OOXML. The toplevel element is expected + to be oMath (handle oMathPara outside of this code). + */ +class SmOoxmlImport +{ +public: + explicit SmOoxmlImport( oox::formulaimport::XmlStream& stream ); + OUString ConvertToStarMath(); +private: + OUString handleStream(); + OUString handleAcc(); + OUString handleBar(); + OUString handleBox(); + OUString handleBorderBox(); + OUString handleD(); + OUString handleEqArr(); + OUString handleF(); + OUString handleFunc(); + enum LimLowUpp_t { LimLow, LimUpp }; + OUString handleLimLowUpp( LimLowUpp_t limlowupp ); + OUString handleGroupChr(); + OUString handleM(); + OUString handleNary(); + OUString handleR(); + OUString handleRad(); + OUString handleSpre(); + OUString handleSsub(); + OUString handleSsubsup(); + OUString handleSsup(); + OUString readOMathArg( int stoptoken ); + OUString readOMathArgInElement( int token ); + + oox::formulaimport::XmlStream& m_rStream; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/parse.cxx b/starmath/source/parse.cxx new file mode 100644 index 0000000000..66daec9d24 --- /dev/null +++ b/starmath/source/parse.cxx @@ -0,0 +1,52 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <parse.hxx> +#include <parse5.hxx> +#include <smmod.hxx> +#include <cfgitem.hxx> + +AbstractSmParser* starmathdatabase::GetDefaultSmParser() +{ + switch(SM_MOD()->GetConfig()->GetDefaultSmSyntaxVersion()) + { + case 5: + { + AbstractSmParser* aParser = new SmParser5(); + return aParser; + } + default: + throw std::range_error("parser version limit"); + } +} + +AbstractSmParser* starmathdatabase::GetVersionSmParser(sal_uInt16 nVersion) +{ + switch(nVersion) + { + case 5: + { + AbstractSmParser* aParser = new SmParser5(); + return aParser; + } + default: + throw std::range_error("parser version limit"); + } +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/parse5.cxx b/starmath/source/parse5.cxx new file mode 100644 index 0000000000..8f2815c3c3 --- /dev/null +++ b/starmath/source/parse5.cxx @@ -0,0 +1,2823 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <com/sun/star/i18n/UnicodeType.hpp> +#include <com/sun/star/i18n/KParseTokens.hpp> +#include <com/sun/star/i18n/KParseType.hpp> +#include <i18nlangtag/lang.h> +#include <tools/lineend.hxx> +#include <unotools/configmgr.hxx> +#include <unotools/syslocale.hxx> +#include <osl/diagnose.h> +#include <rtl/character.hxx> +#include <parse5.hxx> +#include <strings.hrc> +#include <smmod.hxx> +#include <symbol.hxx> +#include <cfgitem.hxx> +#include <starmathdatabase.hxx> + +#include <stack> + +using namespace ::com::sun::star::i18n; + +//Definition of math keywords +const SmTokenTableEntry aTokenTable[] + = { { u"abs"_ustr, TABS, '\0', TG::UnOper, 13 }, + { u"acute"_ustr, TACUTE, MS_ACUTE, TG::Attribute, 5 }, + { u"aleph"_ustr, TALEPH, MS_ALEPH, TG::Standalone, 5 }, + { u"alignb"_ustr, TALIGNC, '\0', TG::Align, 0 }, + { u"alignc"_ustr, TALIGNC, '\0', TG::Align, 0 }, + { u"alignl"_ustr, TALIGNL, '\0', TG::Align, 0 }, + { u"alignm"_ustr, TALIGNC, '\0', TG::Align, 0 }, + { u"alignr"_ustr, TALIGNR, '\0', TG::Align, 0 }, + { u"alignt"_ustr, TALIGNC, '\0', TG::Align, 0 }, + { u"and"_ustr, TAND, MS_AND, TG::Product, 0 }, + { u"approx"_ustr, TAPPROX, MS_APPROX, TG::Relation, 0 }, + { u"arccos"_ustr, TACOS, '\0', TG::Function, 5 }, + { u"arccot"_ustr, TACOT, '\0', TG::Function, 5 }, + { u"arcosh"_ustr, TACOSH, '\0', TG::Function, 5 }, + { u"arcoth"_ustr, TACOTH, '\0', TG::Function, 5 }, + { u"arcsin"_ustr, TASIN, '\0', TG::Function, 5 }, + { u"arctan"_ustr, TATAN, '\0', TG::Function, 5 }, + { u"arsinh"_ustr, TASINH, '\0', TG::Function, 5 }, + { u"artanh"_ustr, TATANH, '\0', TG::Function, 5 }, + { u"backepsilon"_ustr, TBACKEPSILON, MS_BACKEPSILON, TG::Standalone, 5 }, + { u"bar"_ustr, TBAR, MS_BAR, TG::Attribute, 5 }, + { u"binom"_ustr, TBINOM, '\0', TG::NONE, 5 }, + { u"bold"_ustr, TBOLD, '\0', TG::FontAttr, 5 }, + { u"boper"_ustr, TBOPER, '\0', TG::Product, 0 }, + { u"breve"_ustr, TBREVE, MS_BREVE, TG::Attribute, 5 }, + { u"bslash"_ustr, TBACKSLASH, MS_BACKSLASH, TG::Product, 0 }, + { u"cdot"_ustr, TCDOT, MS_CDOT, TG::Product, 0 }, + { u"check"_ustr, TCHECK, MS_CHECK, TG::Attribute, 5 }, + { u"circ"_ustr, TCIRC, MS_CIRC, TG::Standalone, 5 }, + { u"circle"_ustr, TCIRCLE, MS_CIRCLE, TG::Attribute, 5 }, + { u"color"_ustr, TCOLOR, '\0', TG::FontAttr, 5 }, + { u"coprod"_ustr, TCOPROD, MS_COPROD, TG::Oper, 5 }, + { u"cos"_ustr, TCOS, '\0', TG::Function, 5 }, + { u"cosh"_ustr, TCOSH, '\0', TG::Function, 5 }, + { u"cot"_ustr, TCOT, '\0', TG::Function, 5 }, + { u"coth"_ustr, TCOTH, '\0', TG::Function, 5 }, + { u"csub"_ustr, TCSUB, '\0', TG::Power, 0 }, + { u"csup"_ustr, TCSUP, '\0', TG::Power, 0 }, + { u"dddot"_ustr, TDDDOT, MS_DDDOT, TG::Attribute, 5 }, + { u"ddot"_ustr, TDDOT, MS_DDOT, TG::Attribute, 5 }, + { u"def"_ustr, TDEF, MS_DEF, TG::Relation, 0 }, + { u"div"_ustr, TDIV, MS_DIV, TG::Product, 0 }, + { u"divides"_ustr, TDIVIDES, MS_LINE, TG::Relation, 0 }, + { u"dlarrow"_ustr, TDLARROW, MS_DLARROW, TG::Standalone, 5 }, + { u"dlrarrow"_ustr, TDLRARROW, MS_DLRARROW, TG::Standalone, 5 }, + { u"dot"_ustr, TDOT, MS_DOT, TG::Attribute, 5 }, + { u"dotsaxis"_ustr, TDOTSAXIS, MS_DOTSAXIS, TG::Standalone, 5 }, // 5 to continue expression + { u"dotsdiag"_ustr, TDOTSDIAG, MS_DOTSUP, TG::Standalone, 5 }, + { u"dotsdown"_ustr, TDOTSDOWN, MS_DOTSDOWN, TG::Standalone, 5 }, + { u"dotslow"_ustr, TDOTSLOW, MS_DOTSLOW, TG::Standalone, 5 }, + { u"dotsup"_ustr, TDOTSUP, MS_DOTSUP, TG::Standalone, 5 }, + { u"dotsvert"_ustr, TDOTSVERT, MS_DOTSVERT, TG::Standalone, 5 }, + { u"downarrow"_ustr, TDOWNARROW, MS_DOWNARROW, TG::Standalone, 5 }, + { u"drarrow"_ustr, TDRARROW, MS_DRARROW, TG::Standalone, 5 }, + { u"emptyset"_ustr, TEMPTYSET, MS_EMPTYSET, TG::Standalone, 5 }, + { u"equiv"_ustr, TEQUIV, MS_EQUIV, TG::Relation, 0 }, + { u"evaluate"_ustr, TEVALUATE, '\0', TG::NONE, 0 }, + { u"exists"_ustr, TEXISTS, MS_EXISTS, TG::Standalone, 5 }, + { u"exp"_ustr, TEXP, '\0', TG::Function, 5 }, + { u"fact"_ustr, TFACT, MS_FACT, TG::UnOper, 5 }, + { u"fixed"_ustr, TFIXED, '\0', TG::Font, 0 }, + { u"font"_ustr, TFONT, '\0', TG::FontAttr, 5 }, + { u"forall"_ustr, TFORALL, MS_FORALL, TG::Standalone, 5 }, + { u"fourier"_ustr, TFOURIER, MS_FOURIER, TG::Standalone, 5 }, + { u"frac"_ustr, TFRAC, '\0', TG::NONE, 5 }, + { u"from"_ustr, TFROM, '\0', TG::Limit, 0 }, + { u"func"_ustr, TFUNC, '\0', TG::Function, 5 }, + { u"ge"_ustr, TGE, MS_GE, TG::Relation, 0 }, + { u"geslant"_ustr, TGESLANT, MS_GESLANT, TG::Relation, 0 }, + { u"gg"_ustr, TGG, MS_GG, TG::Relation, 0 }, + { u"grave"_ustr, TGRAVE, MS_GRAVE, TG::Attribute, 5 }, + { u"gt"_ustr, TGT, MS_GT, TG::Relation, 0 }, + { u"hadd"_ustr, THADD, MS_HADD, TG::Oper, 5 }, + { u"harpoon"_ustr, THARPOON, MS_HARPOON, TG::Attribute, 5 }, + { u"hat"_ustr, THAT, MS_HAT, TG::Attribute, 5 }, + { u"hbar"_ustr, THBAR, MS_HBAR, TG::Standalone, 5 }, + { u"hex"_ustr, THEX, '\0', TG::NONE, 5 }, + { u"iiint"_ustr, TIIINT, MS_IIINT, TG::Oper, 5 }, + { u"iint"_ustr, TIINT, MS_IINT, TG::Oper, 5 }, + { u"im"_ustr, TIM, MS_IM, TG::Standalone, 5 }, + { u"in"_ustr, TIN, MS_IN, TG::Relation, 0 }, + { u"infinity"_ustr, TINFINITY, MS_INFINITY, TG::Standalone, 5 }, + { u"infty"_ustr, TINFINITY, MS_INFINITY, TG::Standalone, 5 }, + { u"int"_ustr, TINT, MS_INT, TG::Oper, 5 }, + { u"intd"_ustr, TINTD, MS_INT, TG::Oper, 5 }, + { u"intersection"_ustr, TINTERSECT, MS_INTERSECT, TG::Product, 0 }, + { u"it"_ustr, TIT, '\0', TG::Product, 0 }, + { u"ital"_ustr, TITALIC, '\0', TG::FontAttr, 5 }, + { u"italic"_ustr, TITALIC, '\0', TG::FontAttr, 5 }, + { u"lambdabar"_ustr, TLAMBDABAR, MS_LAMBDABAR, TG::Standalone, 5 }, + { u"langle"_ustr, TLANGLE, MS_LMATHANGLE, TG::LBrace, 5 }, + { u"laplace"_ustr, TLAPLACE, MS_LAPLACE, TG::Standalone, 5 }, + { u"lbrace"_ustr, TLBRACE, MS_LBRACE, TG::LBrace, 5 }, + { u"lceil"_ustr, TLCEIL, MS_LCEIL, TG::LBrace, 5 }, + { u"ldbracket"_ustr, TLDBRACKET, MS_LDBRACKET, TG::LBrace, 5 }, + { u"ldline"_ustr, TLDLINE, MS_DVERTLINE, TG::LBrace, 5 }, + { u"le"_ustr, TLE, MS_LE, TG::Relation, 0 }, + { u"left"_ustr, TLEFT, '\0', TG::NONE, 5 }, + { u"leftarrow"_ustr, TLEFTARROW, MS_LEFTARROW, TG::Standalone, 5 }, + { u"leslant"_ustr, TLESLANT, MS_LESLANT, TG::Relation, 0 }, + { u"lfloor"_ustr, TLFLOOR, MS_LFLOOR, TG::LBrace, 5 }, + { u"lim"_ustr, TLIM, '\0', TG::Oper, 5 }, + { u"liminf"_ustr, TLIMINF, '\0', TG::Oper, 5 }, + { u"limsup"_ustr, TLIMSUP, '\0', TG::Oper, 5 }, + { u"lint"_ustr, TLINT, MS_LINT, TG::Oper, 5 }, + { u"ll"_ustr, TLL, MS_LL, TG::Relation, 0 }, + { u"lline"_ustr, TLLINE, MS_VERTLINE, TG::LBrace, 5 }, + { u"llint"_ustr, TLLINT, MS_LLINT, TG::Oper, 5 }, + { u"lllint"_ustr, TLLLINT, MS_LLLINT, TG::Oper, 5 }, + { u"ln"_ustr, TLN, '\0', TG::Function, 5 }, + { u"log"_ustr, TLOG, '\0', TG::Function, 5 }, + { u"lrline"_ustr, TLRLINE, MS_VERTLINE, TG::LBrace | TG::RBrace, 5 }, + { u"lrdline"_ustr, TLRDLINE, MS_VERTLINE, TG::LBrace | TG::RBrace, 5 }, + { u"lsub"_ustr, TLSUB, '\0', TG::Power, 0 }, + { u"lsup"_ustr, TLSUP, '\0', TG::Power, 0 }, + { u"lt"_ustr, TLT, MS_LT, TG::Relation, 0 }, + { u"maj"_ustr, TSUM, MS_MAJ, TG::Oper, 5 }, + { u"matrix"_ustr, TMATRIX, '\0', TG::NONE, 5 }, + { u"minusplus"_ustr, TMINUSPLUS, MS_MINUSPLUS, TG::UnOper | TG::Sum, 5 }, + { u"mline"_ustr, TMLINE, MS_VERTLINE, TG::NONE, 0 }, //! not in TG::RBrace, Level 0 + { u"nabla"_ustr, TNABLA, MS_NABLA, TG::Standalone, 5 }, + { u"nbold"_ustr, TNBOLD, '\0', TG::FontAttr, 5 }, + { u"ndivides"_ustr, TNDIVIDES, MS_NDIVIDES, TG::Relation, 0 }, + { u"neg"_ustr, TNEG, MS_NEG, TG::UnOper, 5 }, + { u"neq"_ustr, TNEQ, MS_NEQ, TG::Relation, 0 }, + { u"newline"_ustr, TNEWLINE, '\0', TG::NONE, 0 }, + { u"ni"_ustr, TNI, MS_NI, TG::Relation, 0 }, + { u"nitalic"_ustr, TNITALIC, '\0', TG::FontAttr, 5 }, + { u"none"_ustr, TNONE, '\0', TG::LBrace | TG::RBrace, 0 }, + { u"nospace"_ustr, TNOSPACE, '\0', TG::Standalone, 5 }, + { u"notexists"_ustr, TNOTEXISTS, MS_NOTEXISTS, TG::Standalone, 5 }, + { u"notin"_ustr, TNOTIN, MS_NOTIN, TG::Relation, 0 }, + { u"nprec"_ustr, TNOTPRECEDES, MS_NOTPRECEDES, TG::Relation, 0 }, + { u"nroot"_ustr, TNROOT, MS_SQRT, TG::UnOper, 5 }, + { u"nsubset"_ustr, TNSUBSET, MS_NSUBSET, TG::Relation, 0 }, + { u"nsubseteq"_ustr, TNSUBSETEQ, MS_NSUBSETEQ, TG::Relation, 0 }, + { u"nsucc"_ustr, TNOTSUCCEEDS, MS_NOTSUCCEEDS, TG::Relation, 0 }, + { u"nsupset"_ustr, TNSUPSET, MS_NSUPSET, TG::Relation, 0 }, + { u"nsupseteq"_ustr, TNSUPSETEQ, MS_NSUPSETEQ, TG::Relation, 0 }, + { u"odivide"_ustr, TODIVIDE, MS_ODIVIDE, TG::Product, 0 }, + { u"odot"_ustr, TODOT, MS_ODOT, TG::Product, 0 }, + { u"ominus"_ustr, TOMINUS, MS_OMINUS, TG::Sum, 0 }, + { u"oper"_ustr, TOPER, '\0', TG::Oper, 5 }, + { u"oplus"_ustr, TOPLUS, MS_OPLUS, TG::Sum, 0 }, + { u"or"_ustr, TOR, MS_OR, TG::Sum, 0 }, + { u"ortho"_ustr, TORTHO, MS_ORTHO, TG::Relation, 0 }, + { u"otimes"_ustr, TOTIMES, MS_OTIMES, TG::Product, 0 }, + { u"over"_ustr, TOVER, '\0', TG::Product, 0 }, + { u"overbrace"_ustr, TOVERBRACE, MS_OVERBRACE, TG::Product, 5 }, + { u"overline"_ustr, TOVERLINE, '\0', TG::Attribute, 5 }, + { u"overstrike"_ustr, TOVERSTRIKE, '\0', TG::Attribute, 5 }, + { u"owns"_ustr, TNI, MS_NI, TG::Relation, 0 }, + { u"parallel"_ustr, TPARALLEL, MS_DLINE, TG::Relation, 0 }, + { u"partial"_ustr, TPARTIAL, MS_PARTIAL, TG::Standalone, 5 }, + { u"phantom"_ustr, TPHANTOM, '\0', TG::FontAttr, 5 }, + { u"plusminus"_ustr, TPLUSMINUS, MS_PLUSMINUS, TG::UnOper | TG::Sum, 5 }, + { u"prec"_ustr, TPRECEDES, MS_PRECEDES, TG::Relation, 0 }, + { u"preccurlyeq"_ustr, TPRECEDESEQUAL, MS_PRECEDESEQUAL, TG::Relation, 0 }, + { u"precsim"_ustr, TPRECEDESEQUIV, MS_PRECEDESEQUIV, TG::Relation, 0 }, + { u"prod"_ustr, TPROD, MS_PROD, TG::Oper, 5 }, + { u"prop"_ustr, TPROP, MS_PROP, TG::Relation, 0 }, + { u"rangle"_ustr, TRANGLE, MS_RMATHANGLE, TG::RBrace, 0 }, //! 0 to terminate expression + { u"rbrace"_ustr, TRBRACE, MS_RBRACE, TG::RBrace, 0 }, + { u"rceil"_ustr, TRCEIL, MS_RCEIL, TG::RBrace, 0 }, + { u"rdbracket"_ustr, TRDBRACKET, MS_RDBRACKET, TG::RBrace, 0 }, + { u"rdline"_ustr, TRDLINE, MS_DVERTLINE, TG::RBrace, 0 }, + { u"re"_ustr, TRE, MS_RE, TG::Standalone, 5 }, + { u"rfloor"_ustr, TRFLOOR, MS_RFLOOR, TG::RBrace, 0 }, //! 0 to terminate expression + { u"right"_ustr, TRIGHT, '\0', TG::NONE, 0 }, + { u"rightarrow"_ustr, TRIGHTARROW, MS_RIGHTARROW, TG::Standalone, 5 }, + { u"rline"_ustr, TRLINE, MS_VERTLINE, TG::RBrace, 0 }, //! 0 to terminate expression + { u"rsub"_ustr, TRSUB, '\0', TG::Power, 0 }, + { u"rsup"_ustr, TRSUP, '\0', TG::Power, 0 }, + { u"sans"_ustr, TSANS, '\0', TG::Font, 0 }, + { u"serif"_ustr, TSERIF, '\0', TG::Font, 0 }, + { u"setC"_ustr, TSETC, MS_SETC, TG::Standalone, 5 }, + { u"setminus"_ustr, TSETMINUS, MS_BACKSLASH, TG::Product, 0 }, + { u"setN"_ustr, TSETN, MS_SETN, TG::Standalone, 5 }, + { u"setQ"_ustr, TSETQ, MS_SETQ, TG::Standalone, 5 }, + { u"setquotient"_ustr, TSETQUOTIENT, MS_SLASH, TG::Product, 0 }, + { u"setR"_ustr, TSETR, MS_SETR, TG::Standalone, 5 }, + { u"setZ"_ustr, TSETZ, MS_SETZ, TG::Standalone, 5 }, + { u"sim"_ustr, TSIM, MS_SIM, TG::Relation, 0 }, + { u"simeq"_ustr, TSIMEQ, MS_SIMEQ, TG::Relation, 0 }, + { u"sin"_ustr, TSIN, '\0', TG::Function, 5 }, + { u"sinh"_ustr, TSINH, '\0', TG::Function, 5 }, + { u"size"_ustr, TSIZE, '\0', TG::FontAttr, 5 }, + { u"slash"_ustr, TSLASH, MS_SLASH, TG::Product, 0 }, + { u"sqrt"_ustr, TSQRT, MS_SQRT, TG::UnOper, 5 }, + { u"stack"_ustr, TSTACK, '\0', TG::NONE, 5 }, + { u"sub"_ustr, TRSUB, '\0', TG::Power, 0 }, + { u"subset"_ustr, TSUBSET, MS_SUBSET, TG::Relation, 0 }, + { u"subseteq"_ustr, TSUBSETEQ, MS_SUBSETEQ, TG::Relation, 0 }, + { u"succ"_ustr, TSUCCEEDS, MS_SUCCEEDS, TG::Relation, 0 }, + { u"succcurlyeq"_ustr, TSUCCEEDSEQUAL, MS_SUCCEEDSEQUAL, TG::Relation, 0 }, + { u"succsim"_ustr, TSUCCEEDSEQUIV, MS_SUCCEEDSEQUIV, TG::Relation, 0 }, + { u"sum"_ustr, TSUM, MS_SUM, TG::Oper, 5 }, + { u"sup"_ustr, TRSUP, '\0', TG::Power, 0 }, + { u"supset"_ustr, TSUPSET, MS_SUPSET, TG::Relation, 0 }, + { u"supseteq"_ustr, TSUPSETEQ, MS_SUPSETEQ, TG::Relation, 0 }, + { u"tan"_ustr, TTAN, '\0', TG::Function, 5 }, + { u"tanh"_ustr, TTANH, '\0', TG::Function, 5 }, + { u"tilde"_ustr, TTILDE, MS_TILDE, TG::Attribute, 5 }, + { u"times"_ustr, TTIMES, MS_TIMES, TG::Product, 0 }, + { u"to"_ustr, TTO, '\0', TG::Limit, 0 }, + { u"toward"_ustr, TTOWARD, MS_RIGHTARROW, TG::Relation, 0 }, + { u"transl"_ustr, TTRANSL, MS_TRANSL, TG::Relation, 0 }, + { u"transr"_ustr, TTRANSR, MS_TRANSR, TG::Relation, 0 }, + { u"underbrace"_ustr, TUNDERBRACE, MS_UNDERBRACE, TG::Product, 5 }, + { u"underline"_ustr, TUNDERLINE, '\0', TG::Attribute, 5 }, + { u"union"_ustr, TUNION, MS_UNION, TG::Sum, 0 }, + { u"uoper"_ustr, TUOPER, '\0', TG::UnOper, 5 }, + { u"uparrow"_ustr, TUPARROW, MS_UPARROW, TG::Standalone, 5 }, + { u"vec"_ustr, TVEC, MS_VEC, TG::Attribute, 5 }, + { u"widebslash"_ustr, TWIDEBACKSLASH, MS_BACKSLASH, TG::Product, 0 }, + { u"wideharpoon"_ustr, TWIDEHARPOON, MS_HARPOON, TG::Attribute, 5 }, + { u"widehat"_ustr, TWIDEHAT, MS_HAT, TG::Attribute, 5 }, + { u"wideslash"_ustr, TWIDESLASH, MS_SLASH, TG::Product, 0 }, + { u"widetilde"_ustr, TWIDETILDE, MS_TILDE, TG::Attribute, 5 }, + { u"widevec"_ustr, TWIDEVEC, MS_VEC, TG::Attribute, 5 }, + { u"wp"_ustr, TWP, MS_WP, TG::Standalone, 5 }, + { u"جا"_ustr, TSIN, '\0', TG::Function, 5 }, + { u"جاز"_ustr, TSINH, '\0', TG::Function, 5 }, + { u"جتا"_ustr, TCOS, '\0', TG::Function, 5 }, + { u"جتاز"_ustr, TCOSH, '\0', TG::Function, 5 }, + { u"Øا"_ustr, TSIN, '\0', TG::Function, 5 }, + { u"Øاز"_ustr, TSINH, '\0', TG::Function, 5 }, + { u"Øتا"_ustr, TCOS, '\0', TG::Function, 5 }, + { u"Øتاز"_ustr, TCOSH, '\0', TG::Function, 5 }, + { u"Øد"_ustr, THADD, MS_HADD, TG::Oper, 5 }, + { u"طا"_ustr, TTAN, '\0', TG::Function, 5 }, + { u"طاز"_ustr, TTANH, '\0', TG::Function, 5 }, + { u"طتا"_ustr, TCOT, '\0', TG::Function, 5 }, + { u"طتاز"_ustr, TCOTH, '\0', TG::Function, 5 }, + { u"ظا"_ustr, TTAN, '\0', TG::Function, 5 }, + { u"ظاز"_ustr, TTANH, '\0', TG::Function, 5 }, + { u"ظتا"_ustr, TCOT, '\0', TG::Function, 5 }, + { u"ظتاز"_ustr, TCOTH, '\0', TG::Function, 5 }, + { u"قا"_ustr, TSEC, '\0', TG::Function, 5 }, + { u"قاز"_ustr, TSECH, '\0', TG::Function, 5 }, + { u"قتا"_ustr, TCSC, '\0', TG::Function, 5 }, + { u"قتاز"_ustr, TCSCH, '\0', TG::Function, 5 }, + { u"لو"_ustr, TLOG, '\0', TG::Function, 5 }, + { u"مجـ"_ustr, TSUM, MS_MAJ, TG::Oper, 5 }, + { u"نها"_ustr, TNAHA, '\0', TG::Oper, 5 }, + { u"ٯا"_ustr, TSEC, '\0', TG::Function, 5 }, + { u"ٯاز"_ustr, TSECH, '\0', TG::Function, 5 }, + { u"ٯتا"_ustr, TCSC, '\0', TG::Function, 5 }, + { u"ٯتاز"_ustr, TCSCH, '\0', TG::Function, 5 } }; + +// First character may be any alphabetic +const sal_Int32 coStartFlags = KParseTokens::ANY_LETTER | KParseTokens::IGNORE_LEADING_WS; + +// Continuing characters may be any alphabetic +const sal_Int32 coContFlags = (coStartFlags & ~KParseTokens::IGNORE_LEADING_WS) + | KParseTokens::TWO_DOUBLE_QUOTES_BREAK_STRING; +// First character for numbers, may be any numeric or dot +const sal_Int32 coNumStartFlags + = KParseTokens::ASC_DIGIT | KParseTokens::ASC_DOT | KParseTokens::IGNORE_LEADING_WS; +// Continuing characters for numbers, may be any numeric or dot or comma. +// tdf#127873: additionally accept ',' comma group separator as too many +// existing documents unwittingly may have used that as decimal separator +// in such locales (though it never was as this is always the en-US locale +// and the group separator is only parsed away). +const sal_Int32 coNumContFlags = (coNumStartFlags & ~KParseTokens::IGNORE_LEADING_WS) + | KParseTokens::GROUP_SEPARATOR_IN_NUMBER; +// First character for numbers hexadecimal +const sal_Int32 coNum16StartFlags + = KParseTokens::ASC_DIGIT | KParseTokens::ASC_UPALPHA | KParseTokens::IGNORE_LEADING_WS; + +// Continuing characters for numbers hexadecimal +const sal_Int32 coNum16ContFlags = (coNum16StartFlags & ~KParseTokens::IGNORE_LEADING_WS); +// user-defined char continuing characters may be any alphanumeric or dot. +const sal_Int32 coUserDefinedCharContFlags = KParseTokens::ANY_LETTER_OR_NUMBER + | KParseTokens::ASC_DOT + | KParseTokens::TWO_DOUBLE_QUOTES_BREAK_STRING; + +//Checks if keyword is in the list. +static inline bool findCompare(const SmTokenTableEntry& lhs, const OUString& s) +{ + return s.compareToIgnoreAsciiCase(lhs.aIdent) > 0; +} + +//Returns the SmTokenTableEntry for a keyword +static const SmTokenTableEntry* GetTokenTableEntry(const OUString& rName) +{ + if (rName.isEmpty()) + return nullptr; //avoid null pointer exceptions + //Looks for the first keyword after or equal to rName in alphabetical order. + auto findIter + = std::lower_bound(std::begin(aTokenTable), std::end(aTokenTable), rName, findCompare); + if (findIter != std::end(aTokenTable) && rName.equalsIgnoreAsciiCase(findIter->aIdent)) + return &*findIter; //check is equal + return nullptr; //not found +} + +static bool IsDelimiter(const OUString& rTxt, sal_Int32 nPos) +{ // returns 'true' iff cChar is '\0' or a delimiter + + assert(nPos <= rTxt.getLength()); //index out of range + if (nPos == rTxt.getLength()) + return true; //This is EOF + sal_Unicode cChar = rTxt[nPos]; + + // check if 'cChar' is in the delimiter table + static const sal_Unicode aDelimiterTable[] = { + ' ', '{', '}', '(', ')', '\t', '\n', '\r', '+', '-', '*', '/', '=', '[', + ']', '^', '_', '#', '%', '>', '<', '&', '|', '\\', '"', '~', '`' + }; //reordered by usage (by eye) for nanoseconds saving. + + //checks the array + for (auto const& cDelimiter : aDelimiterTable) + { + if (cDelimiter == cChar) + return true; + } + + //special chars support + sal_Int16 nTypJp = SM_MOD()->GetSysLocale().GetCharClass().getType(rTxt, nPos); + return (nTypJp == css::i18n::UnicodeType::SPACE_SEPARATOR + || nTypJp == css::i18n::UnicodeType::CONTROL); +} + +// checks number used as arguments in Math formulas (e.g. 'size' command) +// Format: no negative numbers, must start with a digit, no exponent notation, ... +static bool lcl_IsNumber(const OUString& rText) +{ + bool bPoint = false; + const sal_Unicode* pBuffer = rText.getStr(); + for (sal_Int32 nPos = 0; nPos < rText.getLength(); nPos++, pBuffer++) + { + const sal_Unicode cChar = *pBuffer; + if (cChar == '.') + { + if (bPoint) + return false; + else + bPoint = true; + } + else if (!rtl::isAsciiDigit(cChar)) + return false; + } + return true; +} +// checks number used as arguments in Math formulas (e.g. 'size' command) +// Format: no negative numbers, must start with a digit, no exponent notation, ... +static bool lcl_IsNotWholeNumber(const OUString& rText) +{ + const sal_Unicode* pBuffer = rText.getStr(); + for (sal_Int32 nPos = 0; nPos < rText.getLength(); nPos++, pBuffer++) + if (!rtl::isAsciiDigit(*pBuffer)) + return true; + return false; +} +// checks hex number used as arguments in Math formulas (e.g. 'hex' command) +// Format: no negative numbers, must start with a digit, no exponent notation, ... +static bool lcl_IsNotWholeNumber16(const OUString& rText) +{ + const sal_Unicode* pBuffer = rText.getStr(); + for (sal_Int32 nPos = 0; nPos < rText.getLength(); nPos++, pBuffer++) + if (!rtl::isAsciiCanonicHexDigit(*pBuffer)) + return true; + return false; +} + +//Text replace onto m_aBufferString +void SmParser5::Replace(sal_Int32 nPos, sal_Int32 nLen, std::u16string_view aText) +{ + assert(nPos + nLen <= m_aBufferString.getLength()); //checks if length allows text replace + + m_aBufferString = m_aBufferString.replaceAt(nPos, nLen, aText); //replace and reindex + sal_Int32 nChg = aText.size() - nLen; + m_nBufferIndex = m_nBufferIndex + nChg; + m_nTokenIndex = m_nTokenIndex + nChg; +} + +void SmParser5::NextToken() //Central part of the parser +{ + sal_Int32 nBufLen = m_aBufferString.getLength(); + ParseResult aRes; + sal_Int32 nRealStart; + bool bCont; + do + { + // skip white spaces + while (UnicodeType::SPACE_SEPARATOR == m_pSysCC->getType(m_aBufferString, m_nBufferIndex)) + ++m_nBufferIndex; + + // Try to parse a number in a locale-independent manner using + // '.' as decimal separator. + // See https://bz.apache.org/ooo/show_bug.cgi?id=45779 + aRes + = m_aNumCC.parsePredefinedToken(KParseType::ASC_NUMBER, m_aBufferString, m_nBufferIndex, + coNumStartFlags, "", coNumContFlags, ""); + + if (aRes.TokenType == 0) + { + // Try again with the default token parsing. + aRes = m_pSysCC->parseAnyToken(m_aBufferString, m_nBufferIndex, coStartFlags, "", + coContFlags, ""); + } + + nRealStart = m_nBufferIndex + aRes.LeadingWhiteSpace; + m_nBufferIndex = nRealStart; + + bCont = false; + if (aRes.TokenType == 0 && nRealStart < nBufLen && '\n' == m_aBufferString[nRealStart]) + { + // keep data needed for tokens row and col entry up to date + ++m_nRow; + m_nBufferIndex = m_nColOff = nRealStart + 1; + bCont = true; + } + else if (aRes.TokenType & KParseType::ONE_SINGLE_CHAR) + { + if (nRealStart + 2 <= nBufLen && m_aBufferString.match("%%", nRealStart)) + { + //SkipComment + m_nBufferIndex = nRealStart + 2; + while (m_nBufferIndex < nBufLen && '\n' != m_aBufferString[m_nBufferIndex]) + ++m_nBufferIndex; + bCont = true; + } + } + + } while (bCont); + + // set index of current token + m_nTokenIndex = m_nBufferIndex; + sal_uInt32 nCol = nRealStart - m_nColOff; + + bool bHandled = true; + if (nRealStart >= nBufLen) + { + m_aCurToken.eType = TEND; + m_aCurToken.cMathChar = u""_ustr; + m_aCurToken.nGroup = TG::NONE; + m_aCurToken.nLevel = 0; + m_aCurToken.aText.clear(); + } + else if (aRes.TokenType & KParseType::ANY_NUMBER) + { + assert(aRes.EndPos > 0); + if (m_aBufferString[aRes.EndPos - 1] == ',' && aRes.EndPos < nBufLen + && m_pSysCC->getType(m_aBufferString, aRes.EndPos) != UnicodeType::SPACE_SEPARATOR) + { + // Comma followed by a non-space char is unlikely for decimal/thousands separator. + --aRes.EndPos; + } + sal_Int32 n = aRes.EndPos - nRealStart; + assert(n >= 0); + m_aCurToken.eType = TNUMBER; + m_aCurToken.cMathChar = u""_ustr; + m_aCurToken.nGroup = TG::NONE; + m_aCurToken.nLevel = 5; + m_aCurToken.aText = m_aBufferString.copy(nRealStart, n); + + SAL_WARN_IF(!IsDelimiter(m_aBufferString, aRes.EndPos), "starmath", + "identifier really finished? (compatibility!)"); + } + else if (aRes.TokenType & KParseType::DOUBLE_QUOTE_STRING) + { + m_aCurToken.eType = TTEXT; + m_aCurToken.cMathChar = u""_ustr; + m_aCurToken.nGroup = TG::NONE; + m_aCurToken.nLevel = 5; + m_aCurToken.aText = aRes.DequotedNameOrString; + nCol++; + } + else if (aRes.TokenType & KParseType::IDENTNAME) + { + sal_Int32 n = aRes.EndPos - nRealStart; + assert(n >= 0); + OUString aName(m_aBufferString.copy(nRealStart, n)); + const SmTokenTableEntry* pEntry = GetTokenTableEntry(aName); + + if (pEntry) + { + m_aCurToken.eType = pEntry->eType; + m_aCurToken.setChar(pEntry->cMathChar); + m_aCurToken.nGroup = pEntry->nGroup; + m_aCurToken.nLevel = pEntry->nLevel; + m_aCurToken.aText = pEntry->aIdent; + } + else + { + m_aCurToken.eType = TIDENT; + m_aCurToken.cMathChar = u""_ustr; + m_aCurToken.nGroup = TG::NONE; + m_aCurToken.nLevel = 5; + m_aCurToken.aText = aName; + + SAL_WARN_IF(!IsDelimiter(m_aBufferString, aRes.EndPos), "starmath", + "identifier really finished? (compatibility!)"); + } + } + else if (aRes.TokenType == 0 && '_' == m_aBufferString[nRealStart]) + { + m_aCurToken.eType = TRSUB; + m_aCurToken.cMathChar = u""_ustr; + m_aCurToken.nGroup = TG::Power; + m_aCurToken.nLevel = 0; + m_aCurToken.aText = "_"; + + aRes.EndPos = nRealStart + 1; + } + else if (aRes.TokenType & KParseType::BOOLEAN) + { + sal_Int32& rnEndPos = aRes.EndPos; + if (rnEndPos - nRealStart <= 2) + { + sal_Unicode ch = m_aBufferString[nRealStart]; + switch (ch) + { + case '<': + { + if (m_aBufferString.match("<<", nRealStart)) + { + m_aCurToken.eType = TLL; + m_aCurToken.setChar(MS_LL); + m_aCurToken.nGroup = TG::Relation; + m_aCurToken.nLevel = 0; + m_aCurToken.aText = "<<"; + + rnEndPos = nRealStart + 2; + } + else if (m_aBufferString.match("<=", nRealStart)) + { + m_aCurToken.eType = TLE; + m_aCurToken.setChar(MS_LE); + m_aCurToken.nGroup = TG::Relation; + m_aCurToken.nLevel = 0; + m_aCurToken.aText = "<="; + + rnEndPos = nRealStart + 2; + } + else if (m_aBufferString.match("<-", nRealStart)) + { + m_aCurToken.eType = TLEFTARROW; + m_aCurToken.setChar(MS_LEFTARROW); + m_aCurToken.nGroup = TG::Standalone; + m_aCurToken.nLevel = 5; + m_aCurToken.aText = "<-"; + + rnEndPos = nRealStart + 2; + } + else if (m_aBufferString.match("<>", nRealStart)) + { + m_aCurToken.eType = TNEQ; + m_aCurToken.setChar(MS_NEQ); + m_aCurToken.nGroup = TG::Relation; + m_aCurToken.nLevel = 0; + m_aCurToken.aText = "<>"; + + rnEndPos = nRealStart + 2; + } + else if (m_aBufferString.match("<?>", nRealStart)) + { + m_aCurToken.eType = TPLACE; + m_aCurToken.setChar(MS_PLACE); + m_aCurToken.nGroup = TG::NONE; + m_aCurToken.nLevel = 5; + m_aCurToken.aText = "<?>"; + + rnEndPos = nRealStart + 3; + } + else + { + m_aCurToken.eType = TLT; + m_aCurToken.setChar(MS_LT); + m_aCurToken.nGroup = TG::Relation; + m_aCurToken.nLevel = 0; + m_aCurToken.aText = "<"; + } + } + break; + case '>': + { + if (m_aBufferString.match(">=", nRealStart)) + { + m_aCurToken.eType = TGE; + m_aCurToken.setChar(MS_GE); + m_aCurToken.nGroup = TG::Relation; + m_aCurToken.nLevel = 0; + m_aCurToken.aText = ">="; + + rnEndPos = nRealStart + 2; + } + else if (m_aBufferString.match(">>", nRealStart)) + { + m_aCurToken.eType = TGG; + m_aCurToken.setChar(MS_GG); + m_aCurToken.nGroup = TG::Relation; + m_aCurToken.nLevel = 0; + m_aCurToken.aText = ">>"; + + rnEndPos = nRealStart + 2; + } + else + { + m_aCurToken.eType = TGT; + m_aCurToken.setChar(MS_GT); + m_aCurToken.nGroup = TG::Relation; + m_aCurToken.nLevel = 0; + m_aCurToken.aText = ">"; + } + } + break; + default: + bHandled = false; + } + } + } + else if (aRes.TokenType & KParseType::ONE_SINGLE_CHAR) + { + sal_Int32& rnEndPos = aRes.EndPos; + if (rnEndPos - nRealStart == 1) + { + sal_Unicode ch = m_aBufferString[nRealStart]; + switch (ch) + { + case '%': + { + //! modifies aRes.EndPos + + OSL_ENSURE(rnEndPos >= nBufLen || '%' != m_aBufferString[rnEndPos], + "unexpected comment start"); + + // get identifier of user-defined character + ParseResult aTmpRes = m_pSysCC->parseAnyToken(m_aBufferString, rnEndPos, + KParseTokens::ANY_LETTER, "", + coUserDefinedCharContFlags, ""); + + sal_Int32 nTmpStart = rnEndPos + aTmpRes.LeadingWhiteSpace; + + // default setting for the case that no identifier + // i.e. a valid symbol-name is following the '%' + // character + m_aCurToken.eType = TTEXT; + m_aCurToken.cMathChar = u""_ustr; + m_aCurToken.nGroup = TG::NONE; + m_aCurToken.nLevel = 5; + m_aCurToken.aText = "%"; + + if (aTmpRes.TokenType & KParseType::IDENTNAME) + { + sal_Int32 n = aTmpRes.EndPos - nTmpStart; + m_aCurToken.eType = TSPECIAL; + m_aCurToken.aText = m_aBufferString.copy(nTmpStart - 1, n + 1); + + OSL_ENSURE(aTmpRes.EndPos > rnEndPos, "empty identifier"); + if (aTmpRes.EndPos > rnEndPos) + rnEndPos = aTmpRes.EndPos; + else + ++rnEndPos; + } + + // if no symbol-name was found we start-over with + // finding the next token right after the '%' sign. + // I.e. we leave rnEndPos unmodified. + } + break; + case '[': + { + m_aCurToken.eType = TLBRACKET; + m_aCurToken.setChar(MS_LBRACKET); + m_aCurToken.nGroup = TG::LBrace; + m_aCurToken.nLevel = 5; + m_aCurToken.aText = "["; + } + break; + case '\\': + { + m_aCurToken.eType = TESCAPE; + m_aCurToken.cMathChar = u""_ustr; + m_aCurToken.nGroup = TG::NONE; + m_aCurToken.nLevel = 5; + m_aCurToken.aText = "\\"; + } + break; + case ']': + { + m_aCurToken.eType = TRBRACKET; + m_aCurToken.setChar(MS_RBRACKET); + m_aCurToken.nGroup = TG::RBrace; + m_aCurToken.nLevel = 0; + m_aCurToken.aText = "]"; + } + break; + case '^': + { + m_aCurToken.eType = TRSUP; + m_aCurToken.cMathChar = u""_ustr; + m_aCurToken.nGroup = TG::Power; + m_aCurToken.nLevel = 0; + m_aCurToken.aText = "^"; + } + break; + case '`': + { + m_aCurToken.eType = TSBLANK; + m_aCurToken.cMathChar = u""_ustr; + m_aCurToken.nGroup = TG::Blank; + m_aCurToken.nLevel = 5; + m_aCurToken.aText = "`"; + } + break; + case '{': + { + m_aCurToken.eType = TLGROUP; + m_aCurToken.setChar(MS_LBRACE); + m_aCurToken.nGroup = TG::NONE; + m_aCurToken.nLevel = 5; + m_aCurToken.aText = "{"; + } + break; + case '|': + { + m_aCurToken.eType = TOR; + m_aCurToken.setChar(MS_OR); + m_aCurToken.nGroup = TG::Sum; + m_aCurToken.nLevel = 0; + m_aCurToken.aText = "|"; + } + break; + case '}': + { + m_aCurToken.eType = TRGROUP; + m_aCurToken.setChar(MS_RBRACE); + m_aCurToken.nGroup = TG::NONE; + m_aCurToken.nLevel = 0; + m_aCurToken.aText = "}"; + } + break; + case '~': + { + m_aCurToken.eType = TBLANK; + m_aCurToken.cMathChar = u""_ustr; + m_aCurToken.nGroup = TG::Blank; + m_aCurToken.nLevel = 5; + m_aCurToken.aText = "~"; + } + break; + case '#': + { + if (m_aBufferString.match("##", nRealStart)) + { + m_aCurToken.eType = TDPOUND; + m_aCurToken.cMathChar = u""_ustr; + m_aCurToken.nGroup = TG::NONE; + m_aCurToken.nLevel = 0; + m_aCurToken.aText = "##"; + + rnEndPos = nRealStart + 2; + } + else + { + m_aCurToken.eType = TPOUND; + m_aCurToken.cMathChar = u""_ustr; + m_aCurToken.nGroup = TG::NONE; + m_aCurToken.nLevel = 0; + m_aCurToken.aText = "#"; + } + } + break; + case '&': + { + m_aCurToken.eType = TAND; + m_aCurToken.setChar(MS_AND); + m_aCurToken.nGroup = TG::Product; + m_aCurToken.nLevel = 0; + m_aCurToken.aText = "&"; + } + break; + case '(': + { + m_aCurToken.eType = TLPARENT; + m_aCurToken.setChar(MS_LPARENT); + m_aCurToken.nGroup = TG::LBrace; + m_aCurToken.nLevel = 5; //! 0 to continue expression + m_aCurToken.aText = "("; + } + break; + case ')': + { + m_aCurToken.eType = TRPARENT; + m_aCurToken.setChar(MS_RPARENT); + m_aCurToken.nGroup = TG::RBrace; + m_aCurToken.nLevel = 0; //! 0 to terminate expression + m_aCurToken.aText = ")"; + } + break; + case '*': + { + m_aCurToken.eType = TMULTIPLY; + m_aCurToken.setChar(MS_MULTIPLY); + m_aCurToken.nGroup = TG::Product; + m_aCurToken.nLevel = 0; + m_aCurToken.aText = "*"; + } + break; + case '+': + { + if (m_aBufferString.match("+-", nRealStart)) + { + m_aCurToken.eType = TPLUSMINUS; + m_aCurToken.setChar(MS_PLUSMINUS); + m_aCurToken.nGroup = TG::UnOper | TG::Sum; + m_aCurToken.nLevel = 5; + m_aCurToken.aText = "+-"; + + rnEndPos = nRealStart + 2; + } + else + { + m_aCurToken.eType = TPLUS; + m_aCurToken.setChar(MS_PLUS); + m_aCurToken.nGroup = TG::UnOper | TG::Sum; + m_aCurToken.nLevel = 5; + m_aCurToken.aText = "+"; + } + } + break; + case '-': + { + if (m_aBufferString.match("-+", nRealStart)) + { + m_aCurToken.eType = TMINUSPLUS; + m_aCurToken.setChar(MS_MINUSPLUS); + m_aCurToken.nGroup = TG::UnOper | TG::Sum; + m_aCurToken.nLevel = 5; + m_aCurToken.aText = "-+"; + + rnEndPos = nRealStart + 2; + } + else if (m_aBufferString.match("->", nRealStart)) + { + m_aCurToken.eType = TRIGHTARROW; + m_aCurToken.setChar(MS_RIGHTARROW); + m_aCurToken.nGroup = TG::Standalone; + m_aCurToken.nLevel = 5; + m_aCurToken.aText = "->"; + + rnEndPos = nRealStart + 2; + } + else + { + m_aCurToken.eType = TMINUS; + m_aCurToken.setChar(MS_MINUS); + m_aCurToken.nGroup = TG::UnOper | TG::Sum; + m_aCurToken.nLevel = 5; + m_aCurToken.aText = "-"; + } + } + break; + case '.': + { + // Only one character? Then it can't be a number. + if (m_nBufferIndex < m_aBufferString.getLength() - 1) + { + // for compatibility with SO5.2 + // texts like .34 ...56 ... h ...78..90 + // will be treated as numbers + m_aCurToken.eType = TNUMBER; + m_aCurToken.cMathChar = u""_ustr; + m_aCurToken.nGroup = TG::NONE; + m_aCurToken.nLevel = 5; + + sal_Int32 nTxtStart = m_nBufferIndex; + sal_Unicode cChar; + // if the equation ends with dot(.) then increment m_nBufferIndex till end of string only + do + { + cChar = m_aBufferString[++m_nBufferIndex]; + } while ((cChar == '.' || rtl::isAsciiDigit(cChar)) + && (m_nBufferIndex < m_aBufferString.getLength() - 1)); + + m_aCurToken.aText + = m_aBufferString.copy(nTxtStart, m_nBufferIndex - nTxtStart); + aRes.EndPos = m_nBufferIndex; + } + else + bHandled = false; + } + break; + case '/': + { + m_aCurToken.eType = TDIVIDEBY; + m_aCurToken.setChar(MS_SLASH); + m_aCurToken.nGroup = TG::Product; + m_aCurToken.nLevel = 0; + m_aCurToken.aText = "/"; + } + break; + case '=': + { + m_aCurToken.eType = TASSIGN; + m_aCurToken.setChar(MS_ASSIGN); + m_aCurToken.nGroup = TG::Relation; + m_aCurToken.nLevel = 0; + m_aCurToken.aText = "="; + } + break; + default: + bHandled = false; + } + } + } + else + bHandled = false; + + if (!bHandled) + { + m_aCurToken.eType = TCHARACTER; + m_aCurToken.cMathChar = u""_ustr; + m_aCurToken.nGroup = TG::NONE; + m_aCurToken.nLevel = 5; + + // tdf#129372: we may have to deal with surrogate pairs + // (see https://en.wikipedia.org/wiki/Universal_Character_Set_characters#Surrogates) + // in this case, we must read 2 sal_Unicode instead of 1 + int nOffset(rtl::isSurrogate(m_aBufferString[nRealStart]) ? 2 : 1); + m_aCurToken.aText = m_aBufferString.copy(nRealStart, nOffset); + + aRes.EndPos = nRealStart + nOffset; + } + m_aCurESelection = ESelection(m_nRow, nCol, m_nRow, nCol + m_aCurToken.aText.getLength()); + + if (TEND != m_aCurToken.eType) + m_nBufferIndex = aRes.EndPos; +} + +void SmParser5::NextTokenColor(SmTokenType dvipload) +{ + sal_Int32 nBufLen = m_aBufferString.getLength(); + ParseResult aRes; + sal_Int32 nRealStart; + bool bCont; + + do + { + // skip white spaces + while (UnicodeType::SPACE_SEPARATOR == m_pSysCC->getType(m_aBufferString, m_nBufferIndex)) + ++m_nBufferIndex; + //parse, there are few options, so less strict. + aRes = m_pSysCC->parseAnyToken(m_aBufferString, m_nBufferIndex, coStartFlags, "", + coContFlags, ""); + nRealStart = m_nBufferIndex + aRes.LeadingWhiteSpace; + m_nBufferIndex = nRealStart; + bCont = false; + if (aRes.TokenType == 0 && nRealStart < nBufLen && '\n' == m_aBufferString[nRealStart]) + { + // keep data needed for tokens row and col entry up to date + ++m_nRow; + m_nBufferIndex = m_nColOff = nRealStart + 1; + bCont = true; + } + else if (aRes.TokenType & KParseType::ONE_SINGLE_CHAR) + { + if (nRealStart + 2 <= nBufLen && m_aBufferString.match("%%", nRealStart)) + { + //SkipComment + m_nBufferIndex = nRealStart + 2; + while (m_nBufferIndex < nBufLen && '\n' != m_aBufferString[m_nBufferIndex]) + ++m_nBufferIndex; + bCont = true; + } + } + } while (bCont); + + // set index of current token + m_nTokenIndex = m_nBufferIndex; + sal_uInt32 nCol = nRealStart - m_nColOff; + + if (nRealStart >= nBufLen) + m_aCurToken.eType = TEND; + else if (aRes.TokenType & KParseType::IDENTNAME) + { + sal_Int32 n = aRes.EndPos - nRealStart; + assert(n >= 0); + OUString aName(m_aBufferString.copy(nRealStart, n)); + switch (dvipload) + { + case TCOLOR: + m_aCurToken = starmathdatabase::Identify_ColorName_Parser(aName); + break; + case TDVIPSNAMESCOL: + m_aCurToken = starmathdatabase::Identify_ColorName_DVIPSNAMES(aName); + break; + default: + m_aCurToken = starmathdatabase::Identify_ColorName_Parser(aName); + break; + } + } + else if (aRes.TokenType & KParseType::ONE_SINGLE_CHAR) + { + if (m_aBufferString[nRealStart] == '#' && !m_aBufferString.match("##", nRealStart)) + { + m_aCurToken.eType = THEX; + m_aCurToken.cMathChar = u""_ustr; + m_aCurToken.nGroup = TG::Color; + m_aCurToken.nLevel = 0; + m_aCurToken.aText = "hex"; + } + } + else + m_aCurToken.eType = TNONE; + + m_aCurESelection = ESelection(m_nRow, nCol, m_nRow, nCol + m_aCurToken.aText.getLength()); + if (TEND != m_aCurToken.eType) + m_nBufferIndex = aRes.EndPos; +} + +void SmParser5::NextTokenFontSize() +{ + sal_Int32 nBufLen = m_aBufferString.getLength(); + ParseResult aRes; + sal_Int32 nRealStart; + bool bCont; + bool hex = false; + + do + { + // skip white spaces + while (UnicodeType::SPACE_SEPARATOR == m_pSysCC->getType(m_aBufferString, m_nBufferIndex)) + ++m_nBufferIndex; + //hexadecimal parser + aRes = m_pSysCC->parseAnyToken(m_aBufferString, m_nBufferIndex, coNum16StartFlags, ".", + coNum16ContFlags, ".,"); + if (aRes.TokenType == 0) + { + // Try again with the default token parsing. + aRes = m_pSysCC->parseAnyToken(m_aBufferString, m_nBufferIndex, coStartFlags, "", + coContFlags, ""); + } + else + hex = true; + nRealStart = m_nBufferIndex + aRes.LeadingWhiteSpace; + m_nBufferIndex = nRealStart; + bCont = false; + if (aRes.TokenType == 0 && nRealStart < nBufLen && '\n' == m_aBufferString[nRealStart]) + { + // keep data needed for tokens row and col entry up to date + ++m_nRow; + m_nBufferIndex = m_nColOff = nRealStart + 1; + bCont = true; + } + else if (aRes.TokenType & KParseType::ONE_SINGLE_CHAR) + { + if (nRealStart + 2 <= nBufLen && m_aBufferString.match("%%", nRealStart)) + { + //SkipComment + m_nBufferIndex = nRealStart + 2; + while (m_nBufferIndex < nBufLen && '\n' != m_aBufferString[m_nBufferIndex]) + ++m_nBufferIndex; + bCont = true; + } + } + } while (bCont); + + // set index of current token + m_nTokenIndex = m_nBufferIndex; + sal_uInt32 nCol = nRealStart - m_nColOff; + + if (nRealStart >= nBufLen) + m_aCurToken.eType = TEND; + else if (aRes.TokenType & KParseType::ONE_SINGLE_CHAR) + { + if (aRes.EndPos - nRealStart == 1) + { + switch (m_aBufferString[nRealStart]) + { + case '*': + m_aCurToken.eType = TMULTIPLY; + m_aCurToken.setChar(MS_MULTIPLY); + m_aCurToken.nGroup = TG::Product; + m_aCurToken.nLevel = 0; + m_aCurToken.aText = "*"; + break; + case '+': + m_aCurToken.eType = TPLUS; + m_aCurToken.setChar(MS_PLUS); + m_aCurToken.nGroup = TG::UnOper | TG::Sum; + m_aCurToken.nLevel = 5; + m_aCurToken.aText = "+"; + break; + case '-': + m_aCurToken.eType = TMINUS; + m_aCurToken.setChar(MS_MINUS); + m_aCurToken.nGroup = TG::UnOper | TG::Sum; + m_aCurToken.nLevel = 5; + m_aCurToken.aText = "-"; + break; + case '/': + m_aCurToken.eType = TDIVIDEBY; + m_aCurToken.setChar(MS_SLASH); + m_aCurToken.nGroup = TG::Product; + m_aCurToken.nLevel = 0; + m_aCurToken.aText = "/"; + break; + default: + m_aCurToken.eType = TNONE; + break; + } + } + else + m_aCurToken.eType = TNONE; + } + else if (hex) + { + assert(aRes.EndPos > 0); + sal_Int32 n = aRes.EndPos - nRealStart; + assert(n >= 0); + m_aCurToken.eType = THEX; + m_aCurToken.cMathChar = u""_ustr; + m_aCurToken.nGroup = TG::NONE; + m_aCurToken.nLevel = 5; + m_aCurToken.aText = m_aBufferString.copy(nRealStart, n); + } + else + m_aCurToken.eType = TNONE; + + m_aCurESelection = ESelection(m_nRow, nCol, m_nRow, nCol + m_aCurToken.aText.getLength()); + if (TEND != m_aCurToken.eType) + m_nBufferIndex = aRes.EndPos; +} + +namespace +{ +SmNodeArray buildNodeArray(std::vector<std::unique_ptr<SmNode>>& rSubNodes) +{ + SmNodeArray aSubArray(rSubNodes.size()); + for (size_t i = 0; i < rSubNodes.size(); ++i) + aSubArray[i] = rSubNodes[i].release(); + return aSubArray; +} +} //end namespace + +// grammar +/*************************************************************************************************/ + +std::unique_ptr<SmTableNode> SmParser5::DoTable() +{ + DepthProtect aDepthGuard(m_nParseDepth); + + std::vector<std::unique_ptr<SmNode>> aLineArray; + aLineArray.push_back(DoLine()); + while (m_aCurToken.eType == TNEWLINE) + { + NextToken(); + aLineArray.push_back(DoLine()); + } + assert(m_aCurToken.eType == TEND); + std::unique_ptr<SmTableNode> xSNode(new SmTableNode(m_aCurToken)); + xSNode->SetSelection(m_aCurESelection); + xSNode->SetSubNodes(buildNodeArray(aLineArray)); + return xSNode; +} + +std::unique_ptr<SmNode> SmParser5::DoAlign(bool bUseExtraSpaces) +// parse alignment info (if any), then go on with rest of expression +{ + DepthProtect aDepthGuard(m_nParseDepth); + + std::unique_ptr<SmStructureNode> xSNode; + + if (TokenInGroup(TG::Align)) + { + xSNode.reset(new SmAlignNode(m_aCurToken)); + xSNode->SetSelection(m_aCurESelection); + + NextToken(); + + // allow for just one align statement in 5.0 + if (TokenInGroup(TG::Align)) + return DoError(SmParseError::DoubleAlign); + } + + auto pNode = DoExpression(bUseExtraSpaces); + + if (xSNode) + { + xSNode->SetSubNode(0, pNode.release()); + return xSNode; + } + return pNode; +} + +// Postcondition: m_aCurToken.eType == TEND || m_aCurToken.eType == TNEWLINE +std::unique_ptr<SmNode> SmParser5::DoLine() +{ + DepthProtect aDepthGuard(m_nParseDepth); + + std::vector<std::unique_ptr<SmNode>> ExpressionArray; + + // start with single expression that may have an alignment statement + // (and go on with expressions that must not have alignment + // statements in 'while' loop below. See also 'Expression()'.) + if (m_aCurToken.eType != TEND && m_aCurToken.eType != TNEWLINE) + ExpressionArray.push_back(DoAlign()); + + while (m_aCurToken.eType != TEND && m_aCurToken.eType != TNEWLINE) + ExpressionArray.push_back(DoExpression()); + + //If there's no expression, add an empty one. + //this is to avoid a formula tree without any caret + //positions, in visual formula editor. + if (ExpressionArray.empty()) + { + SmToken aTok; + aTok.eType = TNEWLINE; + ExpressionArray.emplace_back(std::unique_ptr<SmNode>(new SmExpressionNode(aTok))); + } + + auto xSNode = std::make_unique<SmLineNode>(m_aCurToken); + xSNode->SetSelection(m_aCurESelection); + xSNode->SetSubNodes(buildNodeArray(ExpressionArray)); + return xSNode; +} + +std::unique_ptr<SmNode> SmParser5::DoExpression(bool bUseExtraSpaces) +{ + DepthProtect aDepthGuard(m_nParseDepth); + + std::vector<std::unique_ptr<SmNode>> RelationArray; + RelationArray.push_back(DoRelation()); + while (m_aCurToken.nLevel >= 4) + RelationArray.push_back(DoRelation()); + + if (RelationArray.size() > 1) + { + std::unique_ptr<SmExpressionNode> xSNode(new SmExpressionNode(m_aCurToken)); + xSNode->SetSubNodes(buildNodeArray(RelationArray)); + xSNode->SetUseExtraSpaces(bUseExtraSpaces); + return xSNode; + } + else + { + // This expression has only one node so just push this node. + return std::move(RelationArray[0]); + } +} + +std::unique_ptr<SmNode> SmParser5::DoRelation() +{ + DepthProtect aDepthGuard(m_nParseDepth); + + int nDepthLimit = m_nParseDepth; + + auto xFirst = DoSum(); + while (TokenInGroup(TG::Relation)) + { + std::unique_ptr<SmStructureNode> xSNode(new SmBinHorNode(m_aCurToken)); + xSNode->SetSelection(m_aCurESelection); + auto xSecond = DoOpSubSup(); + auto xThird = DoSum(); + xSNode->SetSubNodes(std::move(xFirst), std::move(xSecond), std::move(xThird)); + xFirst = std::move(xSNode); + + ++m_nParseDepth; + DepthProtect bDepthGuard(m_nParseDepth); + } + + m_nParseDepth = nDepthLimit; + + return xFirst; +} + +std::unique_ptr<SmNode> SmParser5::DoSum() +{ + DepthProtect aDepthGuard(m_nParseDepth); + + int nDepthLimit = m_nParseDepth; + + auto xFirst = DoProduct(); + while (TokenInGroup(TG::Sum)) + { + std::unique_ptr<SmStructureNode> xSNode(new SmBinHorNode(m_aCurToken)); + xSNode->SetSelection(m_aCurESelection); + auto xSecond = DoOpSubSup(); + auto xThird = DoProduct(); + xSNode->SetSubNodes(std::move(xFirst), std::move(xSecond), std::move(xThird)); + xFirst = std::move(xSNode); + + ++m_nParseDepth; + DepthProtect bDepthGuard(m_nParseDepth); + } + + m_nParseDepth = nDepthLimit; + + return xFirst; +} + +std::unique_ptr<SmNode> SmParser5::DoProduct() +{ + DepthProtect aDepthGuard(m_nParseDepth); + + auto xFirst = DoPower(); + + int nDepthLimit = 0; + + while (TokenInGroup(TG::Product)) + { + //this linear loop builds a recursive structure, if it gets + //too deep then later processing, e.g. releasing the tree, + //can exhaust stack + if (m_nParseDepth + nDepthLimit > DEPTH_LIMIT) + throw std::range_error("parser depth limit"); + + std::unique_ptr<SmStructureNode> xSNode; + std::unique_ptr<SmNode> xOper; + + SmTokenType eType = m_aCurToken.eType; + switch (eType) + { + case TOVER: + xSNode.reset(new SmBinVerNode(m_aCurToken)); + xSNode->SetSelection(m_aCurESelection); + xOper.reset(new SmRectangleNode(m_aCurToken)); + xOper->SetSelection(m_aCurESelection); + NextToken(); + break; + + case TBOPER: + xSNode.reset(new SmBinHorNode(m_aCurToken)); + + NextToken(); + + //Let the glyph node know it's a binary operation + m_aCurToken.eType = TBOPER; + m_aCurToken.nGroup = TG::Product; + xOper = DoGlyphSpecial(); + break; + + case TOVERBRACE: + case TUNDERBRACE: + xSNode.reset(new SmVerticalBraceNode(m_aCurToken)); + xSNode->SetSelection(m_aCurESelection); + xOper.reset(new SmMathSymbolNode(m_aCurToken)); + xOper->SetSelection(m_aCurESelection); + + NextToken(); + break; + + case TWIDEBACKSLASH: + case TWIDESLASH: + { + SmBinDiagonalNode* pSTmp = new SmBinDiagonalNode(m_aCurToken); + pSTmp->SetAscending(eType == TWIDESLASH); + xSNode.reset(pSTmp); + + xOper.reset(new SmPolyLineNode(m_aCurToken)); + xOper->SetSelection(m_aCurESelection); + NextToken(); + + break; + } + + default: + xSNode.reset(new SmBinHorNode(m_aCurToken)); + xSNode->SetSelection(m_aCurESelection); + + xOper = DoOpSubSup(); + } + + auto xArg = DoPower(); + xSNode->SetSubNodesBinMo(std::move(xFirst), std::move(xOper), std::move(xArg)); + xFirst = std::move(xSNode); + ++nDepthLimit; + } + return xFirst; +} + +std::unique_ptr<SmNode> SmParser5::DoSubSup(TG nActiveGroup, std::unique_ptr<SmNode> xGivenNode) +{ + DepthProtect aDepthGuard(m_nParseDepth); + + assert(nActiveGroup == TG::Power || nActiveGroup == TG::Limit); + assert(m_aCurToken.nGroup == nActiveGroup); + + std::unique_ptr<SmSubSupNode> pNode(new SmSubSupNode(m_aCurToken)); + pNode->SetSelection(m_aCurESelection); + //! Of course 'm_aCurToken' is just the first sub-/supscript token. + //! It should be of no further interest. The positions of the + //! sub-/supscripts will be identified by the corresponding subnodes + //! index in the 'aSubNodes' array (enum value from 'SmSubSup'). + + pNode->SetUseLimits(nActiveGroup == TG::Limit); + + // initialize subnodes array + std::vector<std::unique_ptr<SmNode>> aSubNodes(1 + SUBSUP_NUM_ENTRIES); + aSubNodes[0] = std::move(xGivenNode); + + // process all sub-/supscripts + int nIndex = 0; + while (TokenInGroup(nActiveGroup)) + { + SmTokenType eType(m_aCurToken.eType); + + switch (eType) + { + case TRSUB: + nIndex = static_cast<int>(RSUB); + break; + case TRSUP: + nIndex = static_cast<int>(RSUP); + break; + case TFROM: + case TCSUB: + nIndex = static_cast<int>(CSUB); + break; + case TTO: + case TCSUP: + nIndex = static_cast<int>(CSUP); + break; + case TLSUB: + nIndex = static_cast<int>(LSUB); + break; + case TLSUP: + nIndex = static_cast<int>(LSUP); + break; + default: + SAL_WARN("starmath", "unknown case"); + } + nIndex++; + assert(1 <= nIndex && nIndex <= SUBSUP_NUM_ENTRIES); + + std::unique_ptr<SmNode> xENode; + if (aSubNodes[nIndex]) // if already occupied at earlier iteration + { + // forget the earlier one, remember an error instead + aSubNodes[nIndex].reset(); + xENode = DoError(SmParseError::DoubleSubsupscript); // this also skips current token. + } + else + { + // skip sub-/supscript token + NextToken(); + } + + // get sub-/supscript node + // (even when we saw a double-sub/supscript error in the above + // in order to minimize mess and continue parsing.) + std::unique_ptr<SmNode> xSNode; + if (eType == TFROM || eType == TTO) + { + // parse limits in old 4.0 and 5.0 style + xSNode = DoRelation(); + } + else + xSNode = DoTerm(true); + + aSubNodes[nIndex] = std::move(xENode ? xENode : xSNode); + } + + pNode->SetSubNodes(buildNodeArray(aSubNodes)); + return pNode; +} + +std::unique_ptr<SmNode> SmParser5::DoSubSupEvaluate(std::unique_ptr<SmNode> xGivenNode) +{ + DepthProtect aDepthGuard(m_nParseDepth); + + std::unique_ptr<SmSubSupNode> pNode(new SmSubSupNode(m_aCurToken)); + pNode->SetSelection(m_aCurESelection); + pNode->SetUseLimits(true); + + // initialize subnodes array + std::vector<std::unique_ptr<SmNode>> aSubNodes(1 + SUBSUP_NUM_ENTRIES); + aSubNodes[0] = std::move(xGivenNode); + + // process all sub-/supscripts + int nIndex = 0; + while (TokenInGroup(TG::Limit)) + { + SmTokenType eType(m_aCurToken.eType); + + switch (eType) + { + case TFROM: + nIndex = static_cast<int>(RSUB); + break; + case TTO: + nIndex = static_cast<int>(RSUP); + break; + default: + SAL_WARN("starmath", "unknown case"); + } + nIndex++; + assert(1 <= nIndex && nIndex <= SUBSUP_NUM_ENTRIES); + + std::unique_ptr<SmNode> xENode; + if (aSubNodes[nIndex]) // if already occupied at earlier iteration + { + // forget the earlier one, remember an error instead + aSubNodes[nIndex].reset(); + xENode = DoError(SmParseError::DoubleSubsupscript); // this also skips current token. + } + else + NextToken(); // skip sub-/supscript token + + // get sub-/supscript node + std::unique_ptr<SmNode> xSNode; + xSNode = DoTerm(true); + + aSubNodes[nIndex] = std::move(xENode ? xENode : xSNode); + } + + pNode->SetSubNodes(buildNodeArray(aSubNodes)); + return pNode; +} + +std::unique_ptr<SmNode> SmParser5::DoOpSubSup() +{ + DepthProtect aDepthGuard(m_nParseDepth); + + // get operator symbol + auto xNode = std::make_unique<SmMathSymbolNode>(m_aCurToken); + xNode->SetSelection(m_aCurESelection); + // skip operator token + NextToken(); + // get sub- supscripts if any + if (m_aCurToken.nGroup == TG::Power) + return DoSubSup(TG::Power, std::move(xNode)); + return xNode; +} + +std::unique_ptr<SmNode> SmParser5::DoPower() +{ + DepthProtect aDepthGuard(m_nParseDepth); + + // get body for sub- supscripts on top of stack + std::unique_ptr<SmNode> xNode(DoTerm(false)); + + if (m_aCurToken.nGroup == TG::Power) + return DoSubSup(TG::Power, std::move(xNode)); + return xNode; +} + +std::unique_ptr<SmBlankNode> SmParser5::DoBlank() +{ + DepthProtect aDepthGuard(m_nParseDepth); + + assert(TokenInGroup(TG::Blank)); + std::unique_ptr<SmBlankNode> pBlankNode(new SmBlankNode(m_aCurToken)); + pBlankNode->SetSelection(m_aCurESelection); + + do + { + pBlankNode->IncreaseBy(m_aCurToken); + NextToken(); + } while (TokenInGroup(TG::Blank)); + + // Ignore trailing spaces, if corresponding option is set + if (m_aCurToken.eType == TNEWLINE + || (m_aCurToken.eType == TEND && !utl::ConfigManager::IsFuzzing() + && SM_MOD()->GetConfig()->IsIgnoreSpacesRight())) + { + pBlankNode->Clear(); + } + return pBlankNode; +} + +std::unique_ptr<SmNode> SmParser5::DoTerm(bool bGroupNumberIdent) +{ + DepthProtect aDepthGuard(m_nParseDepth); + + switch (m_aCurToken.eType) + { + case TESCAPE: + return DoEscape(); + + case TNOSPACE: + case TLGROUP: + { + bool bNoSpace = m_aCurToken.eType == TNOSPACE; + if (bNoSpace) + NextToken(); + if (m_aCurToken.eType != TLGROUP) + return DoTerm(false); // nospace is no longer concerned + + NextToken(); + + // allow for empty group + if (m_aCurToken.eType == TRGROUP) + { + std::unique_ptr<SmStructureNode> xSNode(new SmExpressionNode(m_aCurToken)); + xSNode->SetSelection(m_aCurESelection); + xSNode->SetSubNodes(nullptr, nullptr); + + NextToken(); + return std::unique_ptr<SmNode>(xSNode.release()); + } + + auto pNode = DoAlign(!bNoSpace); + if (m_aCurToken.eType == TRGROUP) + { + NextToken(); + return pNode; + } + auto xSNode = std::make_unique<SmExpressionNode>(m_aCurToken); + xSNode->SetSelection(m_aCurESelection); + std::unique_ptr<SmNode> xError(DoError(SmParseError::RgroupExpected)); + xSNode->SetSubNodes(std::move(pNode), std::move(xError)); + return std::unique_ptr<SmNode>(xSNode.release()); + } + + case TLEFT: + return DoBrace(); + case TEVALUATE: + return DoEvaluate(); + + case TBLANK: + case TSBLANK: + return DoBlank(); + + case TTEXT: + { + auto pNode = std::make_unique<SmTextNode>(m_aCurToken, FNT_TEXT); + pNode->SetSelection(m_aCurESelection); + NextToken(); + return std::unique_ptr<SmNode>(pNode.release()); + } + case TCHARACTER: + { + auto pNode = std::make_unique<SmTextNode>(m_aCurToken, FNT_VARIABLE); + pNode->SetSelection(m_aCurESelection); + NextToken(); + return std::unique_ptr<SmNode>(pNode.release()); + } + case TIDENT: + case TNUMBER: + { + auto pTextNode = std::make_unique<SmTextNode>( + m_aCurToken, m_aCurToken.eType == TNUMBER ? FNT_NUMBER : FNT_VARIABLE); + pTextNode->SetSelection(m_aCurESelection); + if (!bGroupNumberIdent) + { + NextToken(); + return std::unique_ptr<SmNode>(pTextNode.release()); + } + std::vector<std::unique_ptr<SmNode>> aNodes; + // Some people want to be able to write "x_2n" for "x_{2n}" + // although e.g. LaTeX or AsciiMath interpret that as "x_2 n". + // The tokenizer skips whitespaces so we need some additional + // work to distinguish from "x_2 n". + // See https://bz.apache.org/ooo/show_bug.cgi?id=11752 and + // https://bugs.libreoffice.org/show_bug.cgi?id=55853 + sal_Int32 nBufLen = m_aBufferString.getLength(); + + // We need to be careful to call NextToken() only after having + // tested for a whitespace separator (otherwise it will be + // skipped!) + bool moveToNextToken = true; + while (m_nBufferIndex < nBufLen + && m_pSysCC->getType(m_aBufferString, m_nBufferIndex) + != UnicodeType::SPACE_SEPARATOR) + { + NextToken(); + if (m_aCurToken.eType != TNUMBER && m_aCurToken.eType != TIDENT) + { + // Neither a number nor an identifier. We just moved to + // the next token, so no need to do that again. + moveToNextToken = false; + break; + } + aNodes.emplace_back(std::unique_ptr<SmNode>(new SmTextNode( + m_aCurToken, m_aCurToken.eType == TNUMBER ? FNT_NUMBER : FNT_VARIABLE))); + } + if (moveToNextToken) + NextToken(); + if (aNodes.empty()) + return std::unique_ptr<SmNode>(pTextNode.release()); + // We have several concatenated identifiers and numbers. + // Let's group them into one SmExpressionNode. + aNodes.insert(aNodes.begin(), std::move(pTextNode)); + std::unique_ptr<SmExpressionNode> xNode(new SmExpressionNode(SmToken())); + xNode->SetSubNodes(buildNodeArray(aNodes)); + return std::unique_ptr<SmNode>(xNode.release()); + } + case TLEFTARROW: + case TRIGHTARROW: + case TUPARROW: + case TDOWNARROW: + case TCIRC: + case TDRARROW: + case TDLARROW: + case TDLRARROW: + case TEXISTS: + case TNOTEXISTS: + case TFORALL: + case TPARTIAL: + case TNABLA: + case TLAPLACE: + case TFOURIER: + case TTOWARD: + case TDOTSAXIS: + case TDOTSDIAG: + case TDOTSDOWN: + case TDOTSLOW: + case TDOTSUP: + case TDOTSVERT: + { + auto pNode = std::make_unique<SmMathSymbolNode>(m_aCurToken); + pNode->SetSelection(m_aCurESelection); + NextToken(); + return std::unique_ptr<SmNode>(pNode.release()); + } + + case TSETN: + case TSETZ: + case TSETQ: + case TSETR: + case TSETC: + case THBAR: + case TLAMBDABAR: + case TBACKEPSILON: + case TALEPH: + case TIM: + case TRE: + case TWP: + case TEMPTYSET: + case TINFINITY: + { + auto pNode = std::make_unique<SmMathIdentifierNode>(m_aCurToken); + pNode->SetSelection(m_aCurESelection); + NextToken(); + return std::unique_ptr<SmNode>(pNode.release()); + } + + case TPLACE: + { + auto pNode = std::make_unique<SmPlaceNode>(m_aCurToken); + pNode->SetSelection(m_aCurESelection); + NextToken(); + return std::unique_ptr<SmNode>(pNode.release()); + } + + case TSPECIAL: + return DoSpecial(); + + case TBINOM: + return DoBinom(); + + case TFRAC: + return DoFrac(); + + case TSTACK: + return DoStack(); + + case TMATRIX: + return DoMatrix(); + + case THEX: + NextTokenFontSize(); + if (m_aCurToken.eType == THEX) + { + auto pTextNode = std::make_unique<SmTextNode>(m_aCurToken, FNT_NUMBER); + pTextNode->SetSelection(m_aCurESelection); + NextToken(); + return pTextNode; + } + else + return DoError(SmParseError::NumberExpected); + default: + if (TokenInGroup(TG::LBrace)) + return DoBrace(); + if (TokenInGroup(TG::Oper)) + return DoOperator(); + if (TokenInGroup(TG::UnOper)) + return DoUnOper(); + if (TokenInGroup(TG::Attribute) || TokenInGroup(TG::FontAttr)) + { + std::stack<std::unique_ptr<SmStructureNode>, + std::vector<std::unique_ptr<SmStructureNode>>> + aStack; + bool bIsAttr; + for (;;) + { + bIsAttr = TokenInGroup(TG::Attribute); + if (!bIsAttr && !TokenInGroup(TG::FontAttr)) + break; + aStack.push(bIsAttr ? DoAttribute() : DoFontAttribute()); + } + + auto xFirstNode = DoPower(); + while (!aStack.empty()) + { + std::unique_ptr<SmStructureNode> xNode = std::move(aStack.top()); + aStack.pop(); + xNode->SetSubNodes(nullptr, std::move(xFirstNode)); + xFirstNode = std::move(xNode); + } + return xFirstNode; + } + if (TokenInGroup(TG::Function)) + return DoFunction(); + return DoError(SmParseError::UnexpectedChar); + } +} + +std::unique_ptr<SmNode> SmParser5::DoEscape() +{ + DepthProtect aDepthGuard(m_nParseDepth); + + NextToken(); + + switch (m_aCurToken.eType) + { + case TLPARENT: + case TRPARENT: + case TLBRACKET: + case TRBRACKET: + case TLDBRACKET: + case TRDBRACKET: + case TLBRACE: + case TLGROUP: + case TRBRACE: + case TRGROUP: + case TLANGLE: + case TRANGLE: + case TLCEIL: + case TRCEIL: + case TLFLOOR: + case TRFLOOR: + case TLLINE: + case TRLINE: + case TLDLINE: + case TRDLINE: + { + auto pNode = std::make_unique<SmMathSymbolNode>(m_aCurToken); + pNode->SetSelection(m_aCurESelection); + NextToken(); + return std::unique_ptr<SmNode>(pNode.release()); + } + default: + return DoError(SmParseError::UnexpectedToken); + } +} + +std::unique_ptr<SmOperNode> SmParser5::DoOperator() +{ + DepthProtect aDepthGuard(m_nParseDepth); + + assert(TokenInGroup(TG::Oper)); + + auto xSNode = std::make_unique<SmOperNode>(m_aCurToken); + xSNode->SetSelection(m_aCurESelection); + + // get operator + auto xOperator = DoOper(); + + if (m_aCurToken.nGroup == TG::Limit || m_aCurToken.nGroup == TG::Power) + xOperator = DoSubSup(m_aCurToken.nGroup, std::move(xOperator)); + + // get argument + auto xArg = DoPower(); + + xSNode->SetSubNodes(std::move(xOperator), std::move(xArg)); + return xSNode; +} + +std::unique_ptr<SmNode> SmParser5::DoOper() +{ + DepthProtect aDepthGuard(m_nParseDepth); + + SmTokenType eType(m_aCurToken.eType); + std::unique_ptr<SmNode> pNode; + + switch (eType) + { + case TSUM: + case TPROD: + case TCOPROD: + case TINT: + case TINTD: + case TIINT: + case TIIINT: + case TLINT: + case TLLINT: + case TLLLINT: + pNode.reset(new SmMathSymbolNode(m_aCurToken)); + pNode->SetSelection(m_aCurESelection); + break; + + case TLIM: + case TLIMSUP: + case TLIMINF: + case THADD: + case TNAHA: + if (eType == TLIMSUP) + m_aCurToken.aText = u"lim sup"_ustr; + else if (eType == TLIMINF) + m_aCurToken.aText = u"lim inf"_ustr; + else if (eType == TNAHA) + m_aCurToken.aText = u"نها"_ustr; + else if (eType == THADD) + m_aCurToken.aText = OUString(&MS_HADD, 1); + else + m_aCurToken.aText = u"lim"_ustr; + pNode.reset(new SmTextNode(m_aCurToken, FNT_TEXT)); + pNode->SetSelection(m_aCurESelection); + break; + + case TOPER: + NextToken(); + OSL_ENSURE(m_aCurToken.eType == TSPECIAL, "Sm: wrong token"); + m_aCurToken.eType = TOPER; + pNode.reset(new SmGlyphSpecialNode(m_aCurToken)); + pNode->SetSelection(m_aCurESelection); + break; + + default: + assert(false && "unknown case"); + } + + NextToken(); + return pNode; +} + +std::unique_ptr<SmStructureNode> SmParser5::DoUnOper() +{ + DepthProtect aDepthGuard(m_nParseDepth); + + assert(TokenInGroup(TG::UnOper)); + + SmToken aNodeToken = m_aCurToken; + ESelection aESelection = m_aCurESelection; + SmTokenType eType = m_aCurToken.eType; + bool bIsPostfix = eType == TFACT; + + std::unique_ptr<SmStructureNode> xSNode; + std::unique_ptr<SmNode> xOper; + std::unique_ptr<SmNode> xExtra; + std::unique_ptr<SmNode> xArg; + + switch (eType) + { + case TABS: + case TSQRT: + NextToken(); + break; + + case TNROOT: + NextToken(); + xExtra = DoPower(); + break; + + case TUOPER: + NextToken(); + //Let the glyph know what it is... + m_aCurToken.eType = TUOPER; + m_aCurToken.nGroup = TG::UnOper; + xOper = DoGlyphSpecial(); + break; + + case TPLUS: + case TMINUS: + case TPLUSMINUS: + case TMINUSPLUS: + case TNEG: + case TFACT: + xOper = DoOpSubSup(); + break; + + default: + assert(false); + } + + // get argument + xArg = DoPower(); + + if (eType == TABS) + { + xSNode.reset(new SmBraceNode(aNodeToken)); + xSNode->SetSelection(aESelection); + xSNode->SetScaleMode(SmScaleMode::Height); + + // build nodes for left & right lines + // (text, group, level of the used token are of no interest here) + // we'll use row & column of the keyword for abs + aNodeToken.eType = TABS; + + aNodeToken.setChar(MS_VERTLINE); + std::unique_ptr<SmNode> xLeft(new SmMathSymbolNode(aNodeToken)); + xLeft->SetSelection(aESelection); + std::unique_ptr<SmNode> xRight(new SmMathSymbolNode(aNodeToken)); + xRight->SetSelection(aESelection); + + xSNode->SetSubNodes(std::move(xLeft), std::move(xArg), std::move(xRight)); + } + else if (eType == TSQRT || eType == TNROOT) + { + xSNode.reset(new SmRootNode(aNodeToken)); + xSNode->SetSelection(aESelection); + xOper.reset(new SmRootSymbolNode(aNodeToken)); + xOper->SetSelection(aESelection); + xSNode->SetSubNodes(std::move(xExtra), std::move(xOper), std::move(xArg)); + } + else + { + xSNode.reset(new SmUnHorNode(aNodeToken)); + xSNode->SetSelection(aESelection); + if (bIsPostfix) + xSNode->SetSubNodes(std::move(xArg), std::move(xOper)); + else + { + // prefix operator + xSNode->SetSubNodes(std::move(xOper), std::move(xArg)); + } + } + return xSNode; +} + +std::unique_ptr<SmStructureNode> SmParser5::DoAttribute() +{ + DepthProtect aDepthGuard(m_nParseDepth); + + assert(TokenInGroup(TG::Attribute)); + + auto xSNode = std::make_unique<SmAttributeNode>(m_aCurToken); + xSNode->SetSelection(m_aCurESelection); + std::unique_ptr<SmNode> xAttr; + SmScaleMode eScaleMode = SmScaleMode::None; + + // get appropriate node for the attribute itself + switch (m_aCurToken.eType) + { + case TUNDERLINE: + case TOVERLINE: + case TOVERSTRIKE: + xAttr.reset(new SmRectangleNode(m_aCurToken)); + xAttr->SetSelection(m_aCurESelection); + eScaleMode = SmScaleMode::Width; + break; + + case TWIDEVEC: + case TWIDEHARPOON: + case TWIDEHAT: + case TWIDETILDE: + xAttr.reset(new SmMathSymbolNode(m_aCurToken)); + xAttr->SetSelection(m_aCurESelection); + eScaleMode = SmScaleMode::Width; + break; + + default: + xAttr.reset(new SmMathSymbolNode(m_aCurToken)); + xAttr->SetSelection(m_aCurESelection); + } + + NextToken(); + + xSNode->SetSubNodes(std::move(xAttr), nullptr); // the body will be filled later + xSNode->SetScaleMode(eScaleMode); + return xSNode; +} + +std::unique_ptr<SmStructureNode> SmParser5::DoFontAttribute() +{ + DepthProtect aDepthGuard(m_nParseDepth); + + assert(TokenInGroup(TG::FontAttr)); + + switch (m_aCurToken.eType) + { + case TITALIC: + case TNITALIC: + case TBOLD: + case TNBOLD: + case TPHANTOM: + { + auto pNode = std::make_unique<SmFontNode>(m_aCurToken); + pNode->SetSelection(m_aCurESelection); + NextToken(); + return pNode; + } + + case TSIZE: + return DoFontSize(); + + case TFONT: + return DoFont(); + + case TCOLOR: + return DoColor(); + + default: + assert(false); + return {}; + } +} + +std::unique_ptr<SmStructureNode> SmParser5::DoColor() +{ + DepthProtect aDepthGuard(m_nParseDepth); + + assert(m_aCurToken.eType == TCOLOR); + sal_Int32 nBufferIndex = m_nBufferIndex; + NextTokenColor(TCOLOR); + SmToken aToken; + ESelection aESelection; + + if (m_aCurToken.eType == TDVIPSNAMESCOL) + NextTokenColor(TDVIPSNAMESCOL); + if (m_aCurToken.eType == TERROR) + return DoError(SmParseError::ColorExpected); + if (TokenInGroup(TG::Color)) + { + aToken = m_aCurToken; + aESelection = m_aCurESelection; + if (m_aCurToken.eType == TRGB) //loads r, g and b + { + sal_uInt32 nr, ng, nb, nc; + NextTokenFontSize(); + if (lcl_IsNotWholeNumber(m_aCurToken.aText)) + return DoError(SmParseError::ColorExpected); + nr = m_aCurToken.aText.toUInt32(); + if (nr > 255) + return DoError(SmParseError::ColorExpected); + NextTokenFontSize(); + if (lcl_IsNotWholeNumber(m_aCurToken.aText)) + return DoError(SmParseError::ColorExpected); + ng = m_aCurToken.aText.toUInt32(); + if (ng > 255) + return DoError(SmParseError::ColorExpected); + NextTokenFontSize(); + if (lcl_IsNotWholeNumber(m_aCurToken.aText)) + return DoError(SmParseError::ColorExpected); + nb = m_aCurToken.aText.toUInt32(); + if (nb > 255) + return DoError(SmParseError::ColorExpected); + nc = nb | ng << 8 | nr << 16 | sal_uInt32(0) << 24; + aToken.cMathChar = OUString::number(nc, 16); + } + else if (m_aCurToken.eType == TRGBA) //loads r, g and b + { + sal_uInt32 nr, na, ng, nb, nc; + NextTokenFontSize(); + if (lcl_IsNotWholeNumber(m_aCurToken.aText)) + return DoError(SmParseError::ColorExpected); + nr = m_aCurToken.aText.toUInt32(); + if (nr > 255) + return DoError(SmParseError::ColorExpected); + NextTokenFontSize(); + if (lcl_IsNotWholeNumber(m_aCurToken.aText)) + return DoError(SmParseError::ColorExpected); + ng = m_aCurToken.aText.toUInt32(); + if (ng > 255) + return DoError(SmParseError::ColorExpected); + NextTokenFontSize(); + if (lcl_IsNotWholeNumber(m_aCurToken.aText)) + return DoError(SmParseError::ColorExpected); + nb = m_aCurToken.aText.toUInt32(); + if (nb > 255) + return DoError(SmParseError::ColorExpected); + NextTokenFontSize(); + if (lcl_IsNotWholeNumber(m_aCurToken.aText)) + return DoError(SmParseError::ColorExpected); + na = m_aCurToken.aText.toUInt32(); + if (na > 255) + return DoError(SmParseError::ColorExpected); + nc = nb | ng << 8 | nr << 16 | na << 24; + aToken.cMathChar = OUString::number(nc, 16); + } + else if (m_aCurToken.eType == THEX) //loads hex code + { + sal_uInt32 nc; + NextTokenFontSize(); + if (lcl_IsNotWholeNumber16(m_aCurToken.aText)) + return DoError(SmParseError::ColorExpected); + nc = m_aCurToken.aText.toUInt32(16); + aToken.cMathChar = OUString::number(nc, 16); + } + aToken.aText = m_aBufferString.subView(nBufferIndex, m_nBufferIndex - nBufferIndex); + NextToken(); + } + else + return DoError(SmParseError::ColorExpected); + + std::unique_ptr<SmStructureNode> xNode; + xNode.reset(new SmFontNode(aToken)); + xNode->SetSelection(aESelection); + return xNode; +} + +std::unique_ptr<SmStructureNode> SmParser5::DoFont() +{ + DepthProtect aDepthGuard(m_nParseDepth); + + assert(m_aCurToken.eType == TFONT); + + std::unique_ptr<SmStructureNode> xNode; + // last font rules, get that one + SmToken aToken; + ESelection aESelection = m_aCurESelection; + do + { + NextToken(); + + if (TokenInGroup(TG::Font)) + { + aToken = m_aCurToken; + NextToken(); + } + else + { + return DoError(SmParseError::FontExpected); + } + } while (m_aCurToken.eType == TFONT); + + xNode.reset(new SmFontNode(aToken)); + xNode->SetSelection(aESelection); + return xNode; +} + +std::unique_ptr<SmStructureNode> SmParser5::DoFontSize() +{ + DepthProtect aDepthGuard(m_nParseDepth); + std::unique_ptr<SmFontNode> pFontNode(new SmFontNode(m_aCurToken)); + pFontNode->SetSelection(m_aCurESelection); + NextTokenFontSize(); + FontSizeType Type; + + switch (m_aCurToken.eType) + { + case THEX: + Type = FontSizeType::ABSOLUT; + break; + case TPLUS: + Type = FontSizeType::PLUS; + break; + case TMINUS: + Type = FontSizeType::MINUS; + break; + case TMULTIPLY: + Type = FontSizeType::MULTIPLY; + break; + case TDIVIDEBY: + Type = FontSizeType::DIVIDE; + break; + + default: + return DoError(SmParseError::SizeExpected); + } + + if (Type != FontSizeType::ABSOLUT) + { + NextTokenFontSize(); + if (m_aCurToken.eType != THEX) + return DoError(SmParseError::SizeExpected); + } + + // get number argument + Fraction aValue(1); + if (lcl_IsNumber(m_aCurToken.aText)) + { + aValue = m_aCurToken.aText.toDouble(); + //!! Reduce values in order to avoid numerical errors + if (aValue.GetDenominator() > 1000) + { + tools::Long nNum = aValue.GetNumerator(); + tools::Long nDenom = aValue.GetDenominator(); + while (nDenom > 1000) //remove big denominator + { + nNum /= 10; + nDenom /= 10; + } + aValue = Fraction(nNum, nDenom); + } + } + else + return DoError(SmParseError::SizeExpected); + + pFontNode->SetSizeParameter(aValue, Type); + NextToken(); + return pFontNode; +} + +std::unique_ptr<SmStructureNode> SmParser5::DoBrace() +{ + DepthProtect aDepthGuard(m_nParseDepth); + + assert(m_aCurToken.eType == TLEFT || TokenInGroup(TG::LBrace)); + + std::unique_ptr<SmStructureNode> xSNode(new SmBraceNode(m_aCurToken)); + xSNode->SetSelection(m_aCurESelection); + std::unique_ptr<SmNode> pBody, pLeft, pRight; + SmScaleMode eScaleMode = SmScaleMode::None; + SmParseError eError = SmParseError::None; + + if (m_aCurToken.eType == TLEFT) + { + NextToken(); + + eScaleMode = SmScaleMode::Height; + + // check for left bracket + if (TokenInGroup(TG::LBrace) || TokenInGroup(TG::RBrace)) + { + pLeft.reset(new SmMathSymbolNode(m_aCurToken)); + pLeft->SetSelection(m_aCurESelection); + + NextToken(); + pBody = DoBracebody(true); + + if (m_aCurToken.eType == TRIGHT) + { + NextToken(); + + // check for right bracket + if (TokenInGroup(TG::LBrace) || TokenInGroup(TG::RBrace)) + { + pRight.reset(new SmMathSymbolNode(m_aCurToken)); + pRight->SetSelection(m_aCurESelection); + NextToken(); + } + else + eError = SmParseError::RbraceExpected; + } + else + eError = SmParseError::RightExpected; + } + else + eError = SmParseError::LbraceExpected; + } + else + { + assert(TokenInGroup(TG::LBrace)); + + pLeft.reset(new SmMathSymbolNode(m_aCurToken)); + pLeft->SetSelection(m_aCurESelection); + + NextToken(); + pBody = DoBracebody(false); + + SmTokenType eExpectedType = TUNKNOWN; + switch (pLeft->GetToken().eType) + { + case TLPARENT: + eExpectedType = TRPARENT; + break; + case TLBRACKET: + eExpectedType = TRBRACKET; + break; + case TLBRACE: + eExpectedType = TRBRACE; + break; + case TLDBRACKET: + eExpectedType = TRDBRACKET; + break; + case TLLINE: + eExpectedType = TRLINE; + break; + case TLDLINE: + eExpectedType = TRDLINE; + break; + case TLANGLE: + eExpectedType = TRANGLE; + break; + case TLFLOOR: + eExpectedType = TRFLOOR; + break; + case TLCEIL: + eExpectedType = TRCEIL; + break; + case TLRLINE: + eExpectedType = TLRLINE; + break; + case TLRDLINE: + eExpectedType = TLRDLINE; + break; + default: + SAL_WARN("starmath", "unknown case"); + } + + if (m_aCurToken.eType == eExpectedType) + { + pRight.reset(new SmMathSymbolNode(m_aCurToken)); + pRight->SetSelection(m_aCurESelection); + NextToken(); + } + else + eError = SmParseError::ParentMismatch; + } + + if (eError == SmParseError::None) + { + assert(pLeft); + assert(pRight); + xSNode->SetSubNodes(std::move(pLeft), std::move(pBody), std::move(pRight)); + xSNode->SetScaleMode(eScaleMode); + return xSNode; + } + return DoError(eError); +} + +std::unique_ptr<SmBracebodyNode> SmParser5::DoBracebody(bool bIsLeftRight) +{ + DepthProtect aDepthGuard(m_nParseDepth); + + auto pBody = std::make_unique<SmBracebodyNode>(m_aCurToken); + pBody->SetSelection(m_aCurESelection); + + std::vector<std::unique_ptr<SmNode>> aNodes; + // get body if any + if (bIsLeftRight) + { + do + { + if (m_aCurToken.eType == TMLINE) + { + SmMathSymbolNode* pTempNode = new SmMathSymbolNode(m_aCurToken); + pTempNode->SetSelection(m_aCurESelection); + aNodes.emplace_back(std::unique_ptr<SmMathSymbolNode>(pTempNode)); + NextToken(); + } + else if (m_aCurToken.eType != TRIGHT) + { + aNodes.push_back(DoAlign()); + if (m_aCurToken.eType != TMLINE && m_aCurToken.eType != TRIGHT) + aNodes.emplace_back(DoError(SmParseError::RightExpected)); + } + } while (m_aCurToken.eType != TEND && m_aCurToken.eType != TRIGHT); + } + else + { + do + { + if (m_aCurToken.eType == TMLINE) + { + SmMathSymbolNode* pTempNode = new SmMathSymbolNode(m_aCurToken); + pTempNode->SetSelection(m_aCurESelection); + aNodes.emplace_back(std::unique_ptr<SmMathSymbolNode>(pTempNode)); + NextToken(); + } + else if (!TokenInGroup(TG::RBrace)) + { + aNodes.push_back(DoAlign()); + if (m_aCurToken.eType != TMLINE && !TokenInGroup(TG::RBrace)) + aNodes.emplace_back(DoError(SmParseError::RbraceExpected)); + } + } while (m_aCurToken.eType != TEND && !TokenInGroup(TG::RBrace)); + } + + pBody->SetSubNodes(buildNodeArray(aNodes)); + pBody->SetScaleMode(bIsLeftRight ? SmScaleMode::Height : SmScaleMode::None); + return pBody; +} + +std::unique_ptr<SmNode> SmParser5::DoEvaluate() +{ + DepthProtect aDepthGuard(m_nParseDepth); + + // Create node + std::unique_ptr<SmStructureNode> xSNode(new SmBraceNode(m_aCurToken)); + xSNode->SetSelection(m_aCurESelection); + SmToken aToken(TRLINE, MS_VERTLINE, "evaluate", TG::RBrace, 5); + + // Parse body && left none + NextToken(); + std::unique_ptr<SmNode> pBody = DoPower(); + SmToken bToken(TNONE, '\0', "", TG::LBrace, 5); + std::unique_ptr<SmNode> pLeft; + pLeft.reset(new SmMathSymbolNode(bToken)); + + // Mount nodes + std::unique_ptr<SmNode> pRight; + pRight.reset(new SmMathSymbolNode(aToken)); + xSNode->SetSubNodes(std::move(pLeft), std::move(pBody), std::move(pRight)); + xSNode->SetScaleMode(SmScaleMode::Height); // scalable line + + // Parse from to + if (m_aCurToken.nGroup == TG::Limit) + { + std::unique_ptr<SmNode> rSNode; + rSNode = DoSubSupEvaluate(std::move(xSNode)); + rSNode->GetToken().eType = TEVALUATE; + return rSNode; + } + + return xSNode; +} + +std::unique_ptr<SmTextNode> SmParser5::DoFunction() +{ + DepthProtect aDepthGuard(m_nParseDepth); + + if (m_aCurToken.eType == TFUNC) + { + NextToken(); // skip "FUNC"-statement + m_aCurToken.eType = TFUNC; + m_aCurToken.nGroup = TG::Function; + } + auto pNode = std::make_unique<SmTextNode>(m_aCurToken, FNT_FUNCTION); + pNode->SetSelection(m_aCurESelection); + NextToken(); + return pNode; +} + +std::unique_ptr<SmTableNode> SmParser5::DoBinom() +{ + DepthProtect aDepthGuard(m_nParseDepth); + + auto xSNode = std::make_unique<SmTableNode>(m_aCurToken); + xSNode->SetSelection(m_aCurESelection); + + NextToken(); + + auto xFirst = DoSum(); + auto xSecond = DoSum(); + xSNode->SetSubNodes(std::move(xFirst), std::move(xSecond)); + return xSNode; +} + +std::unique_ptr<SmBinVerNode> SmParser5::DoFrac() +{ + DepthProtect aDepthGuard(m_nParseDepth); + + std::unique_ptr<SmBinVerNode> xSNode = std::make_unique<SmBinVerNode>(m_aCurToken); + xSNode->SetSelection(m_aCurESelection); + std::unique_ptr<SmNode> xOper = std::make_unique<SmRectangleNode>(m_aCurToken); + xOper->SetSelection(m_aCurESelection); + + NextToken(); + + auto xFirst = DoSum(); + auto xSecond = DoSum(); + xSNode->SetSubNodes(std::move(xFirst), std::move(xOper), std::move(xSecond)); + return xSNode; +} + +std::unique_ptr<SmStructureNode> SmParser5::DoStack() +{ + DepthProtect aDepthGuard(m_nParseDepth); + + std::unique_ptr<SmStructureNode> xSNode(new SmTableNode(m_aCurToken)); + xSNode->SetSelection(m_aCurESelection); + NextToken(); + if (m_aCurToken.eType != TLGROUP) + return DoError(SmParseError::LgroupExpected); + std::vector<std::unique_ptr<SmNode>> aExprArr; + do + { + NextToken(); + aExprArr.push_back(DoAlign()); + } while (m_aCurToken.eType == TPOUND); + + if (m_aCurToken.eType == TRGROUP) + NextToken(); + else + aExprArr.emplace_back(DoError(SmParseError::RgroupExpected)); + + xSNode->SetSubNodes(buildNodeArray(aExprArr)); + return xSNode; +} + +std::unique_ptr<SmStructureNode> SmParser5::DoMatrix() +{ + DepthProtect aDepthGuard(m_nParseDepth); + + std::unique_ptr<SmMatrixNode> xMNode(new SmMatrixNode(m_aCurToken)); + xMNode->SetSelection(m_aCurESelection); + NextToken(); + if (m_aCurToken.eType != TLGROUP) + return DoError(SmParseError::LgroupExpected); + + std::vector<std::unique_ptr<SmNode>> aExprArr; + do + { + NextToken(); + aExprArr.push_back(DoAlign()); + } while (m_aCurToken.eType == TPOUND); + + size_t nCol = aExprArr.size(); + size_t nRow = 1; + while (m_aCurToken.eType == TDPOUND) + { + NextToken(); + for (size_t i = 0; i < nCol; i++) + { + auto xNode = DoAlign(); + if (i < (nCol - 1)) + { + if (m_aCurToken.eType == TPOUND) + NextToken(); + else + xNode = DoError(SmParseError::PoundExpected); + } + aExprArr.emplace_back(std::move(xNode)); + } + ++nRow; + } + + if (m_aCurToken.eType == TRGROUP) + NextToken(); + else + { + std::unique_ptr<SmNode> xENode(DoError(SmParseError::RgroupExpected)); + if (aExprArr.empty()) + nRow = nCol = 1; + else + aExprArr.pop_back(); + aExprArr.emplace_back(std::move(xENode)); + } + + xMNode->SetSubNodes(buildNodeArray(aExprArr)); + xMNode->SetRowCol(static_cast<sal_uInt16>(nRow), static_cast<sal_uInt16>(nCol)); + return std::unique_ptr<SmStructureNode>(xMNode.release()); +} + +std::unique_ptr<SmSpecialNode> SmParser5::DoSpecial() +{ + DepthProtect aDepthGuard(m_nParseDepth); + + bool bReplace = false; + OUString& rName = m_aCurToken.aText; + OUString aNewName; + + // conversion of symbol names for 6.0 (XML) file format + // (name change on import / export. + // UI uses localized names XML file format does not.) + if (rName.startsWith("%")) + { + if (IsImportSymbolNames()) + { + const SmSym* pSym + = SM_MOD()->GetSymbolManager().GetSymbolByExportName(rName.subView(1)); + if (pSym) + { + aNewName = pSym->GetUiName(); + bReplace = true; + } + } + else if (IsExportSymbolNames()) + { + const SmSym* pSym = SM_MOD()->GetSymbolManager().GetSymbolByUiName(rName.subView(1)); + if (pSym) + { + aNewName = pSym->GetExportName(); + bReplace = true; + } + } + } + if (!aNewName.isEmpty()) + aNewName = "%" + aNewName; + + if (bReplace && !aNewName.isEmpty() && rName != aNewName) + { + Replace(GetTokenIndex(), rName.getLength(), aNewName); + rName = aNewName; + } + + // add symbol name to list of used symbols + const OUString aSymbolName(m_aCurToken.aText.copy(1)); + if (!aSymbolName.isEmpty()) + m_aUsedSymbols.insert(aSymbolName); + + auto pNode = std::make_unique<SmSpecialNode>(m_aCurToken); + pNode->SetSelection(m_aCurESelection); + NextToken(); + return pNode; +} + +std::unique_ptr<SmGlyphSpecialNode> SmParser5::DoGlyphSpecial() +{ + DepthProtect aDepthGuard(m_nParseDepth); + + auto pNode = std::make_unique<SmGlyphSpecialNode>(m_aCurToken); + NextToken(); + return pNode; +} + +std::unique_ptr<SmExpressionNode> SmParser5::DoError(SmParseError eError) +{ + DepthProtect aDepthGuard(m_nParseDepth); + + // Identify error message + OUString sStrBuf(SmResId(RID_ERR_IDENT) + starmathdatabase::getParseErrorDesc(eError)); + + // Generate error node + m_aCurToken.eType = TERROR; + m_aCurToken.cMathChar = sStrBuf; + auto xSNode = std::make_unique<SmExpressionNode>(m_aCurToken); + SmErrorNode* pErr(new SmErrorNode(m_aCurToken)); + pErr->SetSelection(m_aCurESelection); + xSNode->SetSubNode(0, pErr); + + // Append error to the error list + SmErrorDesc aErrDesc(eError, xSNode.get(), m_aCurToken.cMathChar); + m_aErrDescList.push_back(aErrDesc); + + NextToken(); + + return xSNode; +} + +// end grammar + +SmParser5::SmParser5() + : m_nCurError(0) + , m_nBufferIndex(0) + , m_nTokenIndex(0) + , m_nRow(0) + , m_nColOff(0) + , m_bImportSymNames(false) + , m_bExportSymNames(false) + , m_nParseDepth(0) + , m_aNumCC(LanguageTag(LANGUAGE_ENGLISH_US)) + , m_pSysCC(&SM_MOD()->GetSysLocale().GetCharClass()) +{ +} + +SmParser5::~SmParser5() {} + +std::unique_ptr<SmTableNode> SmParser5::Parse(const OUString& rBuffer) +{ + m_aUsedSymbols.clear(); + + m_aBufferString = convertLineEnd(rBuffer, LINEEND_LF); + m_nBufferIndex = 0; + m_nTokenIndex = 0; + m_nRow = 0; + m_nColOff = 0; + m_nCurError = -1; + + m_aErrDescList.clear(); + + NextToken(); + return DoTable(); +} + +std::unique_ptr<SmNode> SmParser5::ParseExpression(const OUString& rBuffer) +{ + m_aBufferString = convertLineEnd(rBuffer, LINEEND_LF); + m_nBufferIndex = 0; + m_nTokenIndex = 0; + m_nRow = 0; + m_nColOff = 0; + m_nCurError = -1; + + m_aErrDescList.clear(); + + NextToken(); + return DoExpression(); +} + +const SmErrorDesc* SmParser5::NextError() +{ + if (!m_aErrDescList.empty()) + if (m_nCurError > 0) + return &m_aErrDescList[--m_nCurError]; + else + { + m_nCurError = 0; + return &m_aErrDescList[m_nCurError]; + } + else + return nullptr; +} + +const SmErrorDesc* SmParser5::PrevError() +{ + if (!m_aErrDescList.empty()) + if (m_nCurError < static_cast<int>(m_aErrDescList.size() - 1)) + return &m_aErrDescList[++m_nCurError]; + else + { + m_nCurError = static_cast<int>(m_aErrDescList.size() - 1); + return &m_aErrDescList[m_nCurError]; + } + else + return nullptr; +} + +const SmErrorDesc* SmParser5::GetError() const +{ + if (m_aErrDescList.empty()) + return nullptr; + return &m_aErrDescList.front(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/parsebase.cxx b/starmath/source/parsebase.cxx new file mode 100644 index 0000000000..31c1e40c9c --- /dev/null +++ b/starmath/source/parsebase.cxx @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <parsebase.hxx> +#include <strings.hrc> +#include <smmod.hxx> + +const TranslateId starmathdatabase::SmParseErrorDesc[] = { + // clang-format off + RID_ERR_NONE, + RID_ERR_UNEXPECTEDCHARACTER, + RID_ERR_UNEXPECTEDTOKEN, + RID_ERR_POUNDEXPECTED, + RID_ERR_COLOREXPECTED, + RID_ERR_LGROUPEXPECTED, + RID_ERR_RGROUPEXPECTED, + RID_ERR_LBRACEEXPECTED, + RID_ERR_RBRACEEXPECTED, + RID_ERR_PARENTMISMATCH, + RID_ERR_RIGHTEXPECTED, + RID_ERR_FONTEXPECTED, + RID_ERR_SIZEEXPECTED, + RID_ERR_DOUBLEALIGN, + RID_ERR_DOUBLESUBSUPSCRIPT, + RID_ERR_NUMBEREXPECTED + // clang-format on +}; + +OUString starmathdatabase::getParseErrorDesc(SmParseError err) +{ + return SmResId(starmathdatabase::SmParseErrorDesc[static_cast<uint_fast8_t>(err)]); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/rect.cxx b/starmath/source/rect.cxx new file mode 100644 index 0000000000..807ab7d0c0 --- /dev/null +++ b/starmath/source/rect.cxx @@ -0,0 +1,621 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <osl/diagnose.h> +#include <o3tl/sorted_vector.hxx> +#include <vcl/metric.hxx> +#include <vcl/svapp.hxx> +#include <vcl/virdev.hxx> +#include <sal/log.hxx> + +#include <format.hxx> +#include <rect.hxx> +#include <types.hxx> +#include <smmod.hxx> + +namespace { + +bool SmGetGlyphBoundRect(const vcl::RenderContext &rDev, + const OUString &rText, tools::Rectangle &rRect) + // basically the same as 'GetTextBoundRect' (in class 'OutputDevice') + // but with a string as argument. +{ + // handle special case first + if (rText.isEmpty()) + { + rRect.SetEmpty(); + return true; + } + + // get a device where 'OutputDevice::GetTextBoundRect' will be successful + OutputDevice *pGlyphDev; + if (rDev.GetOutDevType() != OUTDEV_PRINTER) + pGlyphDev = const_cast<OutputDevice *>(&rDev); + else + { + // since we format for the printer (where GetTextBoundRect will fail) + // we need a virtual device here. + pGlyphDev = &SM_MOD()->GetDefaultVirtualDev(); + } + + const FontMetric aDevFM (rDev.GetFontMetric()); + + pGlyphDev->Push(vcl::PushFlags::FONT | vcl::PushFlags::MAPMODE); + vcl::Font aFnt(rDev.GetFont()); + aFnt.SetAlignment(ALIGN_TOP); + + // use scale factor when calling GetTextBoundRect to counter + // negative effects from antialiasing which may otherwise result + // in significant incorrect bounding rectangles for some characters. + Size aFntSize = aFnt.GetFontSize(); + + // Workaround to avoid HUGE font sizes and resulting problems + tools::Long nScaleFactor = 1; + while( aFntSize.Height() > 2000 * nScaleFactor ) + nScaleFactor *= 2; + + aFnt.SetFontSize( Size( aFntSize.Width() / nScaleFactor, aFntSize.Height() / nScaleFactor ) ); + pGlyphDev->SetFont(aFnt); + + tools::Long nTextWidth = rDev.GetTextWidth(rText); + tools::Rectangle aResult (Point(), Size(nTextWidth, rDev.GetTextHeight())), + aTmp; + + bool bSuccess = pGlyphDev->GetTextBoundRect(aTmp, rText); + OSL_ENSURE( bSuccess, "GetTextBoundRect failed" ); + + + if (!aTmp.IsEmpty()) + { + aResult = tools::Rectangle(aTmp.Left() * nScaleFactor, aTmp.Top() * nScaleFactor, + aTmp.Right() * nScaleFactor, aTmp.Bottom() * nScaleFactor); + if (&rDev != pGlyphDev) /* only when rDev is a printer... */ + { + tools::Long nGDTextWidth = pGlyphDev->GetTextWidth(rText); + if (nGDTextWidth != 0 && + nTextWidth != nGDTextWidth) + { + aResult.SetRight( aResult.Right() * nTextWidth ); + aResult.SetRight( aResult.Right() / ( nGDTextWidth * nScaleFactor) ); + } + } + } + + // move rectangle to match possibly different baselines + // (because of different devices) + tools::Long nDelta = aDevFM.GetAscent() - pGlyphDev->GetFontMetric().GetAscent() * nScaleFactor; + aResult.Move(0, nDelta); + + pGlyphDev->Pop(); + + rRect = aResult; + return bSuccess; +} + +bool SmIsMathAlpha(std::u16string_view aText) + // true iff symbol (from StarMath Font) should be treated as letter +{ + // Set of symbols, which should be treated as letters in StarMath Font + // (to get a normal (non-clipped) SmRect in contrast to the other operators + // and symbols). + static o3tl::sorted_vector<sal_Unicode> const aMathAlpha({ + MS_ALEPH, MS_IM, MS_RE, + MS_WP, u'\xE070', MS_EMPTYSET, + u'\x2113', u'\xE0D6', u'\x2107', + u'\x2127', u'\x210A', MS_HBAR, + MS_LAMBDABAR, MS_SETN, MS_SETZ, + MS_SETQ, MS_SETR, MS_SETC, + u'\x2373', u'\xE0A5', u'\x2112', + u'\x2130', u'\x2131' + }); + + if (aText.empty()) + return false; + + OSL_ENSURE(aText.size() == 1, "Sm : string must be exactly one character long"); + sal_Unicode cChar = aText[0]; + + // is it a greek symbol? + if (u'\xE0AC' <= cChar && cChar <= u'\xE0D4') + return true; + // or, does it appear in 'aMathAlpha'? + return aMathAlpha.find(cChar) != aMathAlpha.end(); +} + +} + + +SmRect::SmRect() + // constructs empty rectangle at (0, 0) with width and height 0. + : aTopLeft(0, 0) + , aSize(0, 0) + , nBaseline(0) + , nAlignT(0) + , nAlignM(0) + , nAlignB(0) + , nGlyphTop(0) + , nGlyphBottom(0) + , nItalicLeftSpace(0) + , nItalicRightSpace(0) + , nLoAttrFence(0) + , nHiAttrFence(0) + , nBorderWidth(0) + , bHasBaseline(false) + , bHasAlignInfo(false) +{ +} + + +void SmRect::CopyAlignInfo(const SmRect &rRect) +{ + nBaseline = rRect.nBaseline; + bHasBaseline = rRect.bHasBaseline; + nAlignT = rRect.nAlignT; + nAlignM = rRect.nAlignM; + nAlignB = rRect.nAlignB; + bHasAlignInfo = rRect.bHasAlignInfo; + nLoAttrFence = rRect.nLoAttrFence; + nHiAttrFence = rRect.nHiAttrFence; +} + + +SmRect::SmRect(const OutputDevice &rDev, const SmFormat *pFormat, + const OUString &rText, sal_uInt16 nBorder) + // get rectangle fitting for drawing 'rText' on OutputDevice 'rDev' + : aTopLeft(0, 0) + , aSize(rDev.GetTextWidth(rText), rDev.GetTextHeight()) +{ + const FontMetric aFM (rDev.GetFontMetric()); + bool bIsMath = aFM.GetFamilyName().equalsIgnoreAsciiCase( FONTNAME_MATH ); + bool bAllowSmaller = bIsMath && !SmIsMathAlpha(rText); + const tools::Long nFontHeight = rDev.GetFont().GetFontSize().Height(); + + nBorderWidth = nBorder; + bHasAlignInfo = true; + bHasBaseline = true; + nBaseline = aFM.GetAscent(); + nAlignT = nBaseline - nFontHeight * 750 / 1000; + nAlignM = nBaseline - nFontHeight * 121 / 422; + // that's where the horizontal bars of '+', '-', ... are + // (1/3 of ascent over baseline) + // (121 = 1/3 of 12pt ascent, 422 = 12pt fontheight) + nAlignB = nBaseline; + + // workaround for printer fonts with very small (possible 0 or even + // negative(!)) leading + if (aFM.GetInternalLeading() < 5 && rDev.GetOutDevType() == OUTDEV_PRINTER) + { + OutputDevice *pWindow = Application::GetDefaultDevice(); + + pWindow->Push(vcl::PushFlags::MAPMODE | vcl::PushFlags::FONT); + + pWindow->SetMapMode(rDev.GetMapMode()); + pWindow->SetFont(rDev.GetFontMetric()); + + tools::Long nDelta = pWindow->GetFontMetric().GetInternalLeading(); + if (nDelta == 0) + { // this value approx. fits a Leading of 80 at a + // Fontheight of 422 (12pt) + nDelta = nFontHeight * 8 / 43; + } + SetTop(GetTop() - nDelta); + + pWindow->Pop(); + } + + // get GlyphBoundRect + tools::Rectangle aGlyphRect; + bool bSuccess = SmGetGlyphBoundRect(rDev, rText, aGlyphRect); + if (!bSuccess) + SAL_WARN("starmath", "Ooops... (Font missing?)"); + + nItalicLeftSpace = GetLeft() - aGlyphRect.Left() + nBorderWidth; + nItalicRightSpace = aGlyphRect.Right() - GetRight() + nBorderWidth; + if (nItalicLeftSpace < 0 && !bAllowSmaller) + nItalicLeftSpace = 0; + if (nItalicRightSpace < 0 && !bAllowSmaller) + nItalicRightSpace = 0; + + tools::Long nDist = 0; + if (pFormat) + nDist = (rDev.GetFont().GetFontSize().Height() + * pFormat->GetDistance(DIS_ORNAMENTSIZE)) / 100; + + nHiAttrFence = aGlyphRect.Top() - 1 - nBorderWidth - nDist; + nLoAttrFence = SmFromTo(GetAlignB(), GetBottom(), 0.0); + + nGlyphTop = aGlyphRect.Top() - nBorderWidth; + nGlyphBottom = aGlyphRect.Bottom() + nBorderWidth; + + if (bAllowSmaller) + { + // for symbols and operators from the StarMath Font + // we adjust upper and lower margin of the symbol + SetTop(nGlyphTop); + SetBottom(nGlyphBottom); + } + + if (nHiAttrFence < GetTop()) + nHiAttrFence = GetTop(); + + if (nLoAttrFence > GetBottom()) + nLoAttrFence = GetBottom(); + + OSL_ENSURE(rText.isEmpty() || !IsEmpty(), + "Sm: empty rectangle created"); +} + + +SmRect::SmRect(tools::Long nWidth, tools::Long nHeight) + // this constructor should never be used for anything textlike because + // it will not provide useful values for baseline, AlignT and AlignB! + // It's purpose is to get a 'SmRect' for the horizontal line in fractions + // as used in 'SmBinVerNode'. + : aTopLeft(0, 0) + , aSize(nWidth, nHeight) + , nBaseline(0) + , nItalicLeftSpace(0) + , nItalicRightSpace(0) + , nBorderWidth(0) + , bHasBaseline(false) + , bHasAlignInfo(true) +{ + nAlignT = nGlyphTop = nHiAttrFence = GetTop(); + nAlignB = nGlyphBottom = nLoAttrFence = GetBottom(); + nAlignM = (nAlignT + nAlignB) / 2; // this is the default +} + + +void SmRect::SetLeft(tools::Long nLeft) +{ + if (nLeft <= GetRight()) + { aSize.setWidth( GetRight() - nLeft + 1 ); + aTopLeft.setX( nLeft ); + } +} + + +void SmRect::SetRight(tools::Long nRight) +{ + if (nRight >= GetLeft()) + aSize.setWidth( nRight - GetLeft() + 1 ); +} + + +void SmRect::SetBottom(tools::Long nBottom) +{ + if (nBottom >= GetTop()) + aSize.setHeight( nBottom - GetTop() + 1 ); +} + + +void SmRect::SetTop(tools::Long nTop) +{ + if (nTop <= GetBottom()) + { aSize.setHeight( GetBottom() - nTop + 1 ); + aTopLeft.setY( nTop ); + } +} + + +void SmRect::Move(const Point &rPosition) + // move rectangle by position 'rPosition'. +{ + aTopLeft += rPosition; + + tools::Long nDelta = rPosition.Y(); + nBaseline += nDelta; + nAlignT += nDelta; + nAlignM += nDelta; + nAlignB += nDelta; + nGlyphTop += nDelta; + nGlyphBottom += nDelta; + nHiAttrFence += nDelta; + nLoAttrFence += nDelta; +} + + +Point SmRect::AlignTo(const SmRect &rRect, RectPos ePos, + RectHorAlign eHor, RectVerAlign eVer) const +{ Point aPos (GetTopLeft()); + // will become the topleft point of the new rectangle position + + // set horizontal or vertical new rectangle position depending on ePos + switch (ePos) + { case RectPos::Left: + aPos.setX( rRect.GetItalicLeft() - GetItalicRightSpace() + - GetWidth() ); + break; + case RectPos::Right: + aPos.setX( rRect.GetItalicRight() + 1 + GetItalicLeftSpace() ); + break; + case RectPos::Top: + aPos.setY( rRect.GetTop() - GetHeight() ); + break; + case RectPos::Bottom: + aPos.setY( rRect.GetBottom() + 1 ); + break; + case RectPos::Attribute: + aPos.setX( rRect.GetItalicCenterX() - GetItalicWidth() / 2 + + GetItalicLeftSpace() ); + break; + default: + assert(false); + } + + // check if horizontal position is already set + if (ePos == RectPos::Left || ePos == RectPos::Right || ePos == RectPos::Attribute) + // correct error in current vertical position + switch (eVer) + { case RectVerAlign::Top : + aPos.AdjustY(rRect.GetAlignT() - GetAlignT() ); + break; + case RectVerAlign::Mid : + aPos.AdjustY(rRect.GetAlignM() - GetAlignM() ); + break; + case RectVerAlign::Baseline : + // align baselines if possible else align mid's + if (HasBaseline() && rRect.HasBaseline()) + aPos.AdjustY(rRect.GetBaseline() - GetBaseline() ); + else + aPos.AdjustY(rRect.GetAlignM() - GetAlignM() ); + break; + case RectVerAlign::Bottom : + aPos.AdjustY(rRect.GetAlignB() - GetAlignB() ); + break; + case RectVerAlign::CenterY : + aPos.AdjustY(rRect.GetCenterY() - GetCenterY() ); + break; + case RectVerAlign::AttributeHi: + aPos.AdjustY(rRect.GetHiAttrFence() - GetBottom() ); + break; + case RectVerAlign::AttributeMid : + aPos.AdjustY(SmFromTo(rRect.GetAlignB(), rRect.GetAlignT(), 0.4) + - GetCenterY() ); + break; + case RectVerAlign::AttributeLo : + aPos.AdjustY(rRect.GetLoAttrFence() - GetTop() ); + break; + default : + assert(false); + } + + // check if vertical position is already set + if (ePos == RectPos::Top || ePos == RectPos::Bottom) + // correct error in current horizontal position + switch (eHor) + { case RectHorAlign::Left: + aPos.AdjustX(rRect.GetItalicLeft() - GetItalicLeft() ); + break; + case RectHorAlign::Center: + aPos.AdjustX(rRect.GetItalicCenterX() - GetItalicCenterX() ); + break; + case RectHorAlign::Right: + aPos.AdjustX(rRect.GetItalicRight() - GetItalicRight() ); + break; + default: + assert(false); + } + + return aPos; +} + + +void SmRect::Union(const SmRect &rRect) + // rectangle union of current one with 'rRect'. The result is to be the + // smallest rectangles that covers the space of both rectangles. + // (empty rectangles cover no space) + //! Italic correction is NOT taken into account here! +{ + if (rRect.IsEmpty()) + return; + + tools::Long nL = rRect.GetLeft(), + nR = rRect.GetRight(), + nT = rRect.GetTop(), + nB = rRect.GetBottom(), + nGT = rRect.nGlyphTop, + nGB = rRect.nGlyphBottom; + if (!IsEmpty()) + { tools::Long nTmp; + + if ((nTmp = GetLeft()) < nL) + nL = nTmp; + if ((nTmp = GetRight()) > nR) + nR = nTmp; + if ((nTmp = GetTop()) < nT) + nT = nTmp; + if ((nTmp = GetBottom()) > nB) + nB = nTmp; + if ((nTmp = nGlyphTop) < nGT) + nGT = nTmp; + if ((nTmp = nGlyphBottom) > nGB) + nGB = nTmp; + } + + SetLeft(nL); + SetRight(nR); + SetTop(nT); + SetBottom(nB); + nGlyphTop = nGT; + nGlyphBottom = nGB; +} + + +SmRect & SmRect::ExtendBy(const SmRect &rRect, RectCopyMBL eCopyMode) + // let current rectangle be the union of itself and 'rRect' + // (the smallest rectangle surrounding both). Also adapt values for + // 'AlignT', 'AlignM', 'AlignB', baseline and italic-spaces. + // The baseline is set according to 'eCopyMode'. + // If one of the rectangles has no relevant info the other one is copied. +{ + // get some values used for (italic) spaces adaptation + // ! (need to be done before changing current SmRect) ! + tools::Long nL = std::min(GetItalicLeft(), rRect.GetItalicLeft()), + nR = std::max(GetItalicRight(), rRect.GetItalicRight()); + + Union(rRect); + + SetItalicSpaces(GetLeft() - nL, nR - GetRight()); + + if (!HasAlignInfo()) + CopyAlignInfo(rRect); + else if (rRect.HasAlignInfo()) + { + assert(HasAlignInfo()); + nAlignT = std::min(GetAlignT(), rRect.GetAlignT()); + nAlignB = std::max(GetAlignB(), rRect.GetAlignB()); + nHiAttrFence = std::min(GetHiAttrFence(), rRect.GetHiAttrFence()); + nLoAttrFence = std::max(GetLoAttrFence(), rRect.GetLoAttrFence()); + + switch (eCopyMode) + { case RectCopyMBL::This: + // already done + break; + case RectCopyMBL::Arg: + CopyMBL(rRect); + break; + case RectCopyMBL::None: + bHasBaseline = false; + nAlignM = (nAlignT + nAlignB) / 2; + break; + case RectCopyMBL::Xor: + if (!HasBaseline()) + CopyMBL(rRect); + break; + default : + assert(false); + } + } + + return *this; +} + + +void SmRect::ExtendBy(const SmRect &rRect, RectCopyMBL eCopyMode, + tools::Long nNewAlignM) + // as 'ExtendBy' but sets AlignM value to 'nNewAlignM'. + // (this version will be used in 'SmBinVerNode' to provide means to + // align eg "{a over b} over c" correctly where AlignM should not + // be (AlignT + AlignB) / 2) +{ + OSL_ENSURE(HasAlignInfo(), "Sm: no align info"); + + ExtendBy(rRect, eCopyMode); + nAlignM = nNewAlignM; +} + + +SmRect & SmRect::ExtendBy(const SmRect &rRect, RectCopyMBL eCopyMode, + bool bKeepVerAlignParams) + // as 'ExtendBy' but keeps original values for AlignT, -M and -B and + // baseline. + // (this is used in 'SmSupSubNode' where the sub-/supscripts shouldn't + // be allowed to modify these values.) +{ + tools::Long nOldAlignT = GetAlignT(), + nOldAlignM = GetAlignM(), + nOldAlignB = GetAlignB(), + nOldBaseline = nBaseline; //! depends not on 'HasBaseline' + bool bOldHasAlignInfo = HasAlignInfo(); + + ExtendBy(rRect, eCopyMode); + + if (bKeepVerAlignParams) + { nAlignT = nOldAlignT; + nAlignM = nOldAlignM; + nAlignB = nOldAlignB; + nBaseline = nOldBaseline; + bHasAlignInfo = bOldHasAlignInfo; + } + + return *this; +} + + +tools::Long SmRect::OrientedDist(const Point &rPoint) const + // return oriented distance of rPoint to the current rectangle, + // especially the return value is <= 0 iff the point is inside the + // rectangle. + // For simplicity the maximum-norm is used. +{ + bool bIsInside = IsInsideItalicRect(rPoint); + + // build reference point to define the distance + Point aRef; + if (bIsInside) + { Point aIC (GetItalicCenterX(), GetCenterY()); + + aRef.setX( rPoint.X() >= aIC.X() ? GetItalicRight() : GetItalicLeft() ); + aRef.setY( rPoint.Y() >= aIC.Y() ? GetBottom() : GetTop() ); + } + else + { + // x-coordinate + if (rPoint.X() > GetItalicRight()) + aRef.setX( GetItalicRight() ); + else if (rPoint.X() < GetItalicLeft()) + aRef.setX( GetItalicLeft() ); + else + aRef.setX( rPoint.X() ); + // y-coordinate + if (rPoint.Y() > GetBottom()) + aRef.setY( GetBottom() ); + else if (rPoint.Y() < GetTop()) + aRef.setY( GetTop() ); + else + aRef.setY( rPoint.Y() ); + } + + // build distance vector + Point aDist (aRef - rPoint); + + tools::Long nAbsX = std::abs(aDist.X()), + nAbsY = std::abs(aDist.Y()); + + return bIsInside ? - std::min(nAbsX, nAbsY) : std::max (nAbsX, nAbsY); +} + + +bool SmRect::IsInsideRect(const Point &rPoint) const +{ + return rPoint.Y() >= GetTop() + && rPoint.Y() <= GetBottom() + && rPoint.X() >= GetLeft() + && rPoint.X() <= GetRight(); +} + + +bool SmRect::IsInsideItalicRect(const Point &rPoint) const +{ + return rPoint.Y() >= GetTop() + && rPoint.Y() <= GetBottom() + && rPoint.X() >= GetItalicLeft() + && rPoint.X() <= GetItalicRight(); +} + +SmRect SmRect::AsGlyphRect() const +{ + SmRect aRect (*this); + aRect.SetTop(nGlyphTop); + aRect.SetBottom(nGlyphBottom); + return aRect; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/rtfexport.cxx b/starmath/source/rtfexport.cxx new file mode 100644 index 0000000000..ee5c486eae --- /dev/null +++ b/starmath/source/rtfexport.cxx @@ -0,0 +1,502 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include "rtfexport.hxx" + +#include <svtools/rtfkeywd.hxx> +#include <filter/msfilter/rtfutil.hxx> + +SmRtfExport::SmRtfExport(const SmNode* pIn) + : SmWordExportBase(pIn) + , m_pBuffer(nullptr) + , m_nEncoding(RTL_TEXTENCODING_DONTKNOW) +{ +} + +void SmRtfExport::ConvertFromStarMath(OStringBuffer& rBuffer, rtl_TextEncoding nEncoding) +{ + if (!GetTree()) + return; + m_pBuffer = &rBuffer; + m_nEncoding = nEncoding; + m_pBuffer->append("{" OOO_STRING_SVTOOLS_RTF_IGNORE LO_STRING_SVTOOLS_RTF_MOMATH " "); + HandleNode(GetTree(), 0); + m_pBuffer->append("}"); // moMath +} + +// NOTE: This is still work in progress and unfinished, but it already covers a good +// part of the rtf math stuff. + +void SmRtfExport::HandleVerticalStack(const SmNode* pNode, int nLevel) +{ + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MEQARR " "); + int size = pNode->GetNumSubNodes(); + for (int i = 0; i < size; ++i) + { + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_ME " "); + HandleNode(pNode->GetSubNode(i), nLevel + 1); + m_pBuffer->append("}"); // me + } + m_pBuffer->append("}"); // meqArr +} + +void SmRtfExport::HandleText(const SmNode* pNode, int /*nLevel*/) +{ + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MR " "); + + if (pNode->GetToken().eType == TTEXT) // literal text + m_pBuffer->append(LO_STRING_SVTOOLS_RTF_MNOR " "); + + auto pTemp = static_cast<const SmTextNode*>(pNode); + SAL_INFO("starmath.rtf", "Text: " << pTemp->GetText()); + for (sal_Int32 i = 0; i < pTemp->GetText().getLength(); i++) + { + sal_uInt16 nChar = pTemp->GetText()[i]; + OUString aValue(SmTextNode::ConvertSymbolToUnicode(nChar)); + m_pBuffer->append(msfilter::rtfutil::OutString(aValue, m_nEncoding)); + } + + m_pBuffer->append("}"); // mr +} + +void SmRtfExport::HandleFractions(const SmNode* pNode, int nLevel, const char* type) +{ + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MF " "); + if (type) + { + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MFPR " "); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MTYPE " "); + m_pBuffer->append(type); + m_pBuffer->append("}"); // mtype + m_pBuffer->append("}"); // mfPr + } + assert(pNode->GetNumSubNodes() == 3); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MNUM " "); + HandleNode(pNode->GetSubNode(0), nLevel + 1); + m_pBuffer->append("}"); // mnum + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MDEN " "); + HandleNode(pNode->GetSubNode(2), nLevel + 1); + m_pBuffer->append("}"); // mden + m_pBuffer->append("}"); // mf +} + +void SmRtfExport::HandleAttribute(const SmAttributeNode* pNode, int nLevel) +{ + switch (pNode->Attribute()->GetToken().eType) + { + case TCHECK: + case TACUTE: + case TGRAVE: + case TBREVE: + case TCIRCLE: + case TVEC: + case TTILDE: + case THAT: + case TDOT: + case TDDOT: + case TDDDOT: + case TWIDETILDE: + case TWIDEHAT: + case TWIDEHARPOON: + case TWIDEVEC: + case TBAR: + { + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MACC " "); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MACCPR " "); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MCHR " "); + OUString aValue(pNode->Attribute()->GetToken().cMathChar); + m_pBuffer->append(msfilter::rtfutil::OutString(aValue, m_nEncoding)); + m_pBuffer->append("}"); // mchr + m_pBuffer->append("}"); // maccPr + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_ME " "); + HandleNode(pNode->Body(), nLevel + 1); + m_pBuffer->append("}"); // me + m_pBuffer->append("}"); // macc + break; + } + case TOVERLINE: + case TUNDERLINE: + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MBAR " "); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MBARPR " "); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MPOS " "); + m_pBuffer->append((pNode->Attribute()->GetToken().eType == TUNDERLINE) ? "bot" : "top"); + m_pBuffer->append("}"); // mpos + m_pBuffer->append("}"); // mbarPr + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_ME " "); + HandleNode(pNode->Body(), nLevel + 1); + m_pBuffer->append("}"); // me + m_pBuffer->append("}"); // mbar + break; + case TOVERSTRIKE: + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MBORDERBOX " "); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MBORDERBOXPR " "); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MHIDETOP " 1}"); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MHIDEBOT " 1}"); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MHIDELEFT " 1}"); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MHIDERIGHT " 1}"); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MSTRIKEH " 1}"); + m_pBuffer->append("}"); // mborderBoxPr + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_ME " "); + HandleNode(pNode->Body(), nLevel + 1); + m_pBuffer->append("}"); // me + m_pBuffer->append("}"); // mborderBox + break; + default: + HandleAllSubNodes(pNode, nLevel); + break; + } +} + +void SmRtfExport::HandleRoot(const SmRootNode* pNode, int nLevel) +{ + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MRAD " "); + if (const SmNode* argument = pNode->Argument()) + { + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MDEG " "); + HandleNode(argument, nLevel + 1); + m_pBuffer->append("}"); // mdeg + } + else + { + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MRADPR " "); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MDEGHIDE " 1}"); + m_pBuffer->append("}"); // mradPr + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MDEG " }"); // empty but present + } + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_ME " "); + HandleNode(pNode->Body(), nLevel + 1); + m_pBuffer->append("}"); // me + m_pBuffer->append("}"); // mrad +} + +namespace +{ +OString mathSymbolToString(const SmNode* node, rtl_TextEncoding nEncoding) +{ + assert(node->GetType() == SmNodeType::Math || node->GetType() == SmNodeType::MathIdent); + auto txtnode = static_cast<const SmTextNode*>(node); + if (txtnode->GetText().isEmpty()) + return {}; + assert(txtnode->GetText().getLength() == 1); + sal_Unicode chr = SmTextNode::ConvertSymbolToUnicode(txtnode->GetText()[0]); + OUString aValue(chr); + return msfilter::rtfutil::OutString(aValue, nEncoding); +} +} + +void SmRtfExport::HandleOperator(const SmOperNode* pNode, int nLevel) +{ + SAL_INFO("starmath.rtf", "Operator: " << int(pNode->GetToken().eType)); + switch (pNode->GetToken().eType) + { + case TINT: + case TINTD: + case TIINT: + case TIIINT: + case TLINT: + case TLLINT: + case TLLLINT: + case TPROD: + case TCOPROD: + case TSUM: + { + const SmSubSupNode* subsup + = pNode->GetSubNode(0)->GetType() == SmNodeType::SubSup + ? static_cast<const SmSubSupNode*>(pNode->GetSubNode(0)) + : nullptr; + const SmNode* operation = subsup ? subsup->GetBody() : pNode->GetSubNode(0); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MNARY " "); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MNARYPR " "); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MCHR " "); + m_pBuffer->append(mathSymbolToString(operation, m_nEncoding)); + m_pBuffer->append("}"); // mchr + if (!subsup || !subsup->GetSubSup(CSUB)) + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MSUBHIDE " 1}"); + if (!subsup || !subsup->GetSubSup(CSUP)) + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MSUPHIDE " 1}"); + m_pBuffer->append("}"); // mnaryPr + if (!subsup || !subsup->GetSubSup(CSUB)) + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MSUB " }"); + else + { + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MSUB " "); + HandleNode(subsup->GetSubSup(CSUB), nLevel + 1); + m_pBuffer->append("}"); // msub + } + if (!subsup || !subsup->GetSubSup(CSUP)) + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MSUP " }"); + else + { + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MSUP " "); + HandleNode(subsup->GetSubSup(CSUP), nLevel + 1); + m_pBuffer->append("}"); // msup + } + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_ME " "); + HandleNode(pNode->GetSubNode(1), nLevel + 1); // body + m_pBuffer->append("}"); // me + m_pBuffer->append("}"); // mnary + break; + } + case TLIM: + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MFUNC " "); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MFNAME " "); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MLIMLOW " "); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_ME " "); + HandleNode(pNode->GetSymbol(), nLevel + 1); + m_pBuffer->append("}"); // me + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MLIM " "); + if (const SmSubSupNode* subsup + = pNode->GetSubNode(0)->GetType() == SmNodeType::SubSup + ? static_cast<const SmSubSupNode*>(pNode->GetSubNode(0)) + : nullptr) + if (subsup->GetSubSup(CSUB)) + HandleNode(subsup->GetSubSup(CSUB), nLevel + 1); + m_pBuffer->append("}"); // mlim + m_pBuffer->append("}"); // mlimLow + m_pBuffer->append("}"); // mfName + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_ME " "); + HandleNode(pNode->GetSubNode(1), nLevel + 1); // body + m_pBuffer->append("}"); // me + m_pBuffer->append("}"); // mfunc + break; + default: + SAL_INFO("starmath.rtf", "TODO: " << __func__ << " unhandled oper type"); + break; + } +} + +void SmRtfExport::HandleSubSupScriptInternal(const SmSubSupNode* pNode, int nLevel, int flags) +{ + // rtf supports only a certain combination of sub/super scripts, but LO can have any, + // so try to merge it using several tags if necessary + if (flags == 0) // none + return; + if ((flags & (1 << RSUP | 1 << RSUB)) == (1 << RSUP | 1 << RSUB)) + { + // m:sSubSup + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MSSUBSUP " "); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_ME " "); + flags &= ~(1 << RSUP | 1 << RSUB); + if (flags == 0) + HandleNode(pNode->GetBody(), nLevel + 1); + else + HandleSubSupScriptInternal(pNode, nLevel, flags); + m_pBuffer->append("}"); // me + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MSUB " "); + HandleNode(pNode->GetSubSup(RSUB), nLevel + 1); + m_pBuffer->append("}"); // msub + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MSUP " "); + HandleNode(pNode->GetSubSup(RSUP), nLevel + 1); + m_pBuffer->append("}"); // msup + m_pBuffer->append("}"); // msubSup + } + else if ((flags & (1 << RSUB)) == 1 << RSUB) + { + // m:sSub + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MSSUB " "); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_ME " "); + flags &= ~(1 << RSUB); + if (flags == 0) + HandleNode(pNode->GetBody(), nLevel + 1); + else + HandleSubSupScriptInternal(pNode, nLevel, flags); + m_pBuffer->append("}"); // me + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MSUB " "); + HandleNode(pNode->GetSubSup(RSUB), nLevel + 1); + m_pBuffer->append("}"); // msub + m_pBuffer->append("}"); // msSub + } + else if ((flags & (1 << RSUP)) == 1 << RSUP) + { + // m:sSup + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MSSUP " "); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_ME " "); + flags &= ~(1 << RSUP); + if (flags == 0) + HandleNode(pNode->GetBody(), nLevel + 1); + else + HandleSubSupScriptInternal(pNode, nLevel, flags); + m_pBuffer->append("}"); // me + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MSUP " "); + HandleNode(pNode->GetSubSup(RSUP), nLevel + 1); + m_pBuffer->append("}"); // msup + m_pBuffer->append("}"); // msSup + } + else if ((flags & (1 << LSUP | 1 << LSUB)) == (1 << LSUP | 1 << LSUB)) + { + // m:sPre + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MSPRE " "); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MSUB " "); + HandleNode(pNode->GetSubSup(LSUB), nLevel + 1); + m_pBuffer->append("}"); // msub + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MSUP " "); + HandleNode(pNode->GetSubSup(LSUP), nLevel + 1); + m_pBuffer->append("}"); // msup + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_ME " "); + flags &= ~(1 << LSUP | 1 << LSUB); + if (flags == 0) + HandleNode(pNode->GetBody(), nLevel + 1); + else + HandleSubSupScriptInternal(pNode, nLevel, flags); + m_pBuffer->append("}"); // me + m_pBuffer->append("}"); // msPre + } + else if ((flags & (1 << CSUB)) == (1 << CSUB)) + { + // m:limLow looks like a good element for central superscript + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MLIMLOW " "); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_ME " "); + flags &= ~(1 << CSUB); + if (flags == 0) + HandleNode(pNode->GetBody(), nLevel + 1); + else + HandleSubSupScriptInternal(pNode, nLevel, flags); + m_pBuffer->append("}"); // me + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MLIM " "); + HandleNode(pNode->GetSubSup(CSUB), nLevel + 1); + m_pBuffer->append("}"); // mlim + m_pBuffer->append("}"); // mlimLow + } + else if ((flags & (1 << CSUP)) == (1 << CSUP)) + { + // m:limUpp looks like a good element for central superscript + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MLIMUPP " "); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_ME " "); + flags &= ~(1 << CSUP); + if (flags == 0) + HandleNode(pNode->GetBody(), nLevel + 1); + else + HandleSubSupScriptInternal(pNode, nLevel, flags); + m_pBuffer->append("}"); // me + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MLIM " "); + HandleNode(pNode->GetSubSup(CSUP), nLevel + 1); + m_pBuffer->append("}"); // mlim + m_pBuffer->append("}"); // mlimUpp + } + else + SAL_INFO("starmath.rtf", "TODO: " << __func__ << " unhandled subsup type"); +} + +void SmRtfExport::HandleMatrix(const SmMatrixNode* pNode, int nLevel) +{ + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MM " "); + for (size_t row = 0; row < pNode->GetNumRows(); ++row) + { + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MMR " "); + for (size_t col = 0; col < pNode->GetNumCols(); ++col) + { + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_ME " "); + if (const SmNode* node = pNode->GetSubNode(row * pNode->GetNumCols() + col)) + HandleNode(node, nLevel + 1); + m_pBuffer->append("}"); // me + } + m_pBuffer->append("}"); // mmr + } + m_pBuffer->append("}"); // mm +} + +void SmRtfExport::HandleBrace(const SmBraceNode* pNode, int nLevel) +{ + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MD " "); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MDPR " "); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MBEGCHR " "); + m_pBuffer->append(mathSymbolToString(pNode->OpeningBrace(), m_nEncoding)); + m_pBuffer->append("}"); // mbegChr + std::vector<const SmNode*> subnodes; + if (pNode->Body()->GetType() == SmNodeType::Bracebody) + { + auto body = static_cast<const SmBracebodyNode*>(pNode->Body()); + bool separatorWritten = false; // assume all separators are the same + for (size_t i = 0; i < body->GetNumSubNodes(); ++i) + { + const SmNode* subnode = body->GetSubNode(i); + if (subnode->GetType() == SmNodeType::Math + || subnode->GetType() == SmNodeType::MathIdent) + { + // do not write, but write what separator it is + auto math = static_cast<const SmMathSymbolNode*>(subnode); + if (!separatorWritten) + { + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MSEPCHR " "); + m_pBuffer->append(mathSymbolToString(math, m_nEncoding)); + m_pBuffer->append("}"); // msepChr + separatorWritten = true; + } + } + else + subnodes.push_back(subnode); + } + } + else + subnodes.push_back(pNode->Body()); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MENDCHR " "); + m_pBuffer->append(mathSymbolToString(pNode->ClosingBrace(), m_nEncoding)); + m_pBuffer->append("}"); // mendChr + m_pBuffer->append("}"); // mdPr + for (const SmNode* subnode : subnodes) + { + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_ME " "); + HandleNode(subnode, nLevel + 1); + m_pBuffer->append("}"); // me + } + m_pBuffer->append("}"); // md +} + +void SmRtfExport::HandleVerticalBrace(const SmVerticalBraceNode* pNode, int nLevel) +{ + SAL_INFO("starmath.rtf", "Vertical: " << int(pNode->GetToken().eType)); + switch (pNode->GetToken().eType) + { + case TOVERBRACE: + case TUNDERBRACE: + { + bool top = (pNode->GetToken().eType == TOVERBRACE); + if (top) + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MLIMUPP " "); + else + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MLIMLOW " "); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_ME " "); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MGROUPCHR " "); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MGROUPCHRPR " "); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MCHR " "); + m_pBuffer->append(mathSymbolToString(pNode->Brace(), m_nEncoding)); + m_pBuffer->append("}"); // mchr + // TODO not sure if pos and vertJc are correct + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MPOS " ") + .append(top ? "top" : "bot") + .append("}"); + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MVERTJC " ") + .append(top ? "bot" : "top") + .append("}"); + m_pBuffer->append("}"); // mgroupChrPr + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_ME " "); + HandleNode(pNode->Body(), nLevel + 1); + m_pBuffer->append("}"); // me + m_pBuffer->append("}"); // mgroupChr + m_pBuffer->append("}"); // me + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MLIM " "); + HandleNode(pNode->Script(), nLevel + 1); + m_pBuffer->append("}"); // mlim + m_pBuffer->append("}"); // mlimUpp or mlimLow + break; + } + default: + SAL_INFO("starmath.rtf", "TODO: " << __func__ << " unhandled vertical brace type"); + break; + } +} + +void SmRtfExport::HandleBlank() +{ + m_pBuffer->append("{" LO_STRING_SVTOOLS_RTF_MR " "); + m_pBuffer->append(" "); + m_pBuffer->append("}"); // mr +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/rtfexport.hxx b/starmath/source/rtfexport.hxx new file mode 100644 index 0000000000..e8c38aaa6c --- /dev/null +++ b/starmath/source/rtfexport.hxx @@ -0,0 +1,42 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#pragma once + +#include "wordexportbase.hxx" + +#include <rtl/strbuf.hxx> + +/** + Class implementing writing of formulas to RTF. + */ +class SmRtfExport : public SmWordExportBase +{ +public: + explicit SmRtfExport(const SmNode* pIn); + void ConvertFromStarMath(OStringBuffer& rBuffer, rtl_TextEncoding nEncoding); + +private: + void HandleVerticalStack(const SmNode* pNode, int nLevel) override; + void HandleText(const SmNode* pNode, int nLevel) override; + void HandleFractions(const SmNode* pNode, int nLevel, const char* type) override; + void HandleRoot(const SmRootNode* pNode, int nLevel) override; + void HandleAttribute(const SmAttributeNode* pNode, int nLevel) override; + void HandleOperator(const SmOperNode* pNode, int nLevel) override; + void HandleSubSupScriptInternal(const SmSubSupNode* pNode, int nLevel, int flags) override; + void HandleMatrix(const SmMatrixNode* pNode, int nLevel) override; + void HandleBrace(const SmBraceNode* pNode, int nLevel) override; + void HandleVerticalBrace(const SmVerticalBraceNode* pNode, int nLevel) override; + void HandleBlank() override; + + OStringBuffer* m_pBuffer; + rtl_TextEncoding m_nEncoding; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/smdetect.cxx b/starmath/source/smdetect.cxx new file mode 100644 index 0000000000..80231c21b7 --- /dev/null +++ b/starmath/source/smdetect.cxx @@ -0,0 +1,148 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "smdetect.hxx" +#include <cppuhelper/supportsservice.hxx> +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/ucb/ContentCreationException.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <sfx2/docfile.hxx> +#include <unotools/mediadescriptor.hxx> +#include <sot/storage.hxx> +#include <comphelper/diagnose_ex.hxx> + +#include "eqnolefilehdr.hxx" + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::io; +using namespace ::com::sun::star::task; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::lang; +using utl::MediaDescriptor; + +SmFilterDetect::SmFilterDetect() +{ +} + +SmFilterDetect::~SmFilterDetect() +{ +} + +OUString SAL_CALL SmFilterDetect::detect( Sequence< PropertyValue >& lDescriptor ) +{ + MediaDescriptor aMediaDesc( lDescriptor ); + uno::Reference< io::XInputStream > xInStream ( aMediaDesc[MediaDescriptor::PROP_INPUTSTREAM], uno::UNO_QUERY ); + if ( !xInStream.is() ) + return OUString(); + + SfxMedium aMedium; + aMedium.UseInteractionHandler( false ); + aMedium.setStreamToLoadFrom( xInStream, true ); + + SvStream *pInStrm = aMedium.GetInStream(); + if ( !pInStrm || pInStrm->GetError() ) + return OUString(); + + // Do not attempt to create an SotStorage on a + // 0-length stream as that would create the compound + // document header on the stream and effectively write to + // disk! + pInStrm->Seek( STREAM_SEEK_TO_BEGIN ); + if ( pInStrm->remainingSize() == 0 ) + return OUString(); + + bool bStorageOk = false; + try + { + tools::SvRef<SotStorage> aStorage = new SotStorage( pInStrm, false ); + bStorageOk = !aStorage->GetError(); + if (bStorageOk) + { + if ( aStorage->IsStream("Equation Native") ) + { + sal_uInt8 nVersion; + if ( GetMathTypeVersion( aStorage.get(), nVersion ) && nVersion <=3 ) + return "math_MathType_3x"; + } + } + } + catch (const css::ucb::ContentCreationException &) + { + TOOLS_WARN_EXCEPTION("starmath", "SmFilterDetect::detect caught" ); + } + + if (!bStorageOk) + { + // 200 should be enough for the XML + // version, encoding and !DOCTYPE + // stuff I hope? + static const sal_uInt16 nBufferSize = 200; + char aBuffer[nBufferSize+1]; + pInStrm->Seek( STREAM_SEEK_TO_BEGIN ); + pInStrm->StartReadingUnicodeText( RTL_TEXTENCODING_DONTKNOW ); // avoid BOM marker + auto nBytesRead = pInStrm->ReadBytes( aBuffer, nBufferSize ); + if (nBytesRead >= 6) + { + aBuffer[nBytesRead] = 0; + bool bIsMathType = false; + if (0 == strncmp( "<?xml", aBuffer, 5)) + bIsMathType = (strstr( aBuffer, "<math>" ) || + strstr( aBuffer, "<math " ) || + strstr( aBuffer, "<math:math " )); + else + // this is the old <math tag to MathML in the beginning of the XML file + bIsMathType = (0 == strncmp( "<math ", aBuffer, 6) || + 0 == strncmp( "<math> ", aBuffer, 7) || + 0 == strncmp( "<math:math> ", aBuffer, 12)); + + if ( bIsMathType ) + return "math_MathML_XML_Math"; + } + } + + return OUString(); +} + +/* XServiceInfo */ +OUString SAL_CALL SmFilterDetect::getImplementationName() +{ + return "com.sun.star.comp.math.FormatDetector"; +} + +/* XServiceInfo */ +sal_Bool SAL_CALL SmFilterDetect::supportsService( const OUString& sServiceName ) +{ + return cppu::supportsService(this, sServiceName); +} + +/* XServiceInfo */ +Sequence< OUString > SAL_CALL SmFilterDetect::getSupportedServiceNames() +{ + return { "com.sun.star.frame.ExtendedTypeDetection" }; +} + +extern "C" SAL_DLLPUBLIC_EXPORT uno::XInterface* +math_FormatDetector_get_implementation(uno::XComponentContext* /*pCtx*/, + uno::Sequence<uno::Any> const& /*rSeq*/) +{ + return cppu::acquire(new SmFilterDetect); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/smdetect.hxx b/starmath/source/smdetect.hxx new file mode 100644 index 0000000000..81a68d9099 --- /dev/null +++ b/starmath/source/smdetect.hxx @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <rtl/ustring.hxx> +#include <com/sun/star/document/XExtendedFilterDetection.hpp> +#include <cppuhelper/implbase.hxx> + +#include <com/sun/star/lang/XServiceInfo.hpp> + + +namespace com::sun::star::beans { struct PropertyValue; } + +class SmFilterDetect : public ::cppu::WeakImplHelper< css::document::XExtendedFilterDetection, css::lang::XServiceInfo > +{ +public: + explicit SmFilterDetect(); + virtual ~SmFilterDetect() override; + + /* XServiceInfo */ + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( const OUString& sServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + // XExtendedFilterDetect + virtual OUString SAL_CALL detect( css::uno::Sequence< css::beans::PropertyValue >& lDescriptor ) override; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/smdll.cxx b/starmath/source/smdll.cxx new file mode 100644 index 0000000000..8c9e88bda4 --- /dev/null +++ b/starmath/source/smdll.cxx @@ -0,0 +1,85 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <svx/svxids.hrc> +#include <svx/modctrl.hxx> +#include <svx/zoomctrl.hxx> +#include <svx/zoomsliderctrl.hxx> +#include <sfx2/docfac.hxx> +#include <sfx2/app.hxx> +#include <sfx2/sidebar/SidebarChildWindow.hxx> + +#include <smdll.hxx> +#include <smmod.hxx> +#include <document.hxx> +#include <view.hxx> + +#include <starmath.hrc> + +#include <svx/xmlsecctrl.hxx> + +namespace +{ + class SmDLL + { + public: + SmDLL(); + }; + + SmDLL::SmDLL() + { + if ( SfxApplication::GetModule(SfxToolsModule::Math) ) // Module already active + return; + + SfxObjectFactory& rFactory = SmDocShell::Factory(); + + auto pUniqueModule = std::make_unique<SmModule>(&rFactory); + SmModule* pModule = pUniqueModule.get(); + SfxApplication::SetModule(SfxToolsModule::Math, std::move(pUniqueModule)); + + rFactory.SetDocumentServiceName( "com.sun.star.formula.FormulaProperties" ); + + SmModule::RegisterInterface(pModule); + SmDocShell::RegisterInterface(pModule); + SmViewShell::RegisterInterface(pModule); + + SmViewShell::RegisterFactory(SFX_INTERFACE_SFXAPP); + + SvxZoomStatusBarControl::RegisterControl(SID_ATTR_ZOOM, pModule); + SvxZoomSliderControl::RegisterControl(SID_ATTR_ZOOMSLIDER, pModule); + SvxModifyControl::RegisterControl(SID_TEXTSTATUS, pModule); + XmlSecStatusBarControl::RegisterControl(SID_SIGNATURE, pModule); + + sfx2::sidebar::SidebarChildWindow::RegisterChildWindow(true, pModule); + + SmCmdBoxWrapper::RegisterChildWindow(true); + } +} + +namespace SmGlobals +{ + void ensure() + { + static SmDLL theDll; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/smediteng.cxx b/starmath/source/smediteng.cxx new file mode 100644 index 0000000000..9eac0863e3 --- /dev/null +++ b/starmath/source/smediteng.cxx @@ -0,0 +1,158 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +#include <smediteng.hxx> +#include <smmod.hxx> +#include <cfgitem.hxx> + +#include <vcl/settings.hxx> +#include <editeng/editview.hxx> +#include <editeng/eeitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/fontitem.hxx> +#include <svl/itempool.hxx> +#include <svl/itemset.hxx> +#include <vcl/outdev.hxx> +#include <vcl/svapp.hxx> + +SmEditEngine::SmEditEngine(SfxItemPool* pItemPool) + : EditEngine(pItemPool) + , m_nOldZoom(100) + , m_nNewZoom(100) + , m_nDefaultFontSize(0) + , m_aAllSelection(0, 0, 0, 0) +{ + SetText(u""_ustr); + + // Add external text leading + SetAddExtLeading(true); + + // Allow to undo changes ( Ctrl + z ) + EnableUndo(true); + + // Length in pixel of a tabulation + SetDefTab(sal_uInt16(Application::GetDefaultDevice()->GetTextWidth("XXXX"))); + + // Set default background color by theme + SetBackgroundColor( + Application::GetDefaultDevice()->GetSettings().GetStyleSettings().GetFieldColor()); + + // Control words + SetControlWord((GetControlWord() | EEControlBits::AUTOINDENTING) + & EEControlBits(~EEControlBits::UNDOATTRIBS) + & EEControlBits(~EEControlBits::PASTESPECIAL)); + + // Word delimiters for auto word selection by double click + SetWordDelimiters(" .=+-*/(){}[];\""); + + // Default mapping mode + SetRefMapMode(MapMode(MapUnit::MapPixel)); + + // Default size of the box + SetPaperSize(Size(1000, 0)); +} + +bool SmEditEngine::checkZoom() +{ + return m_nOldZoom != (m_nNewZoom = SM_MOD()->GetConfig()->GetSmEditWindowZoomFactor()); +} + +void SmEditEngine::executeZoom(EditView* pEditView) +{ + if (checkZoom()) + { + updateZoom(); + if (pEditView) + { + FormatAndLayout(pEditView); + pEditView->SetSelection(pEditView->GetSelection()); + } + } +} + +void SmEditEngine::updateZoom() +{ + // In first run get font size as base to scale + if (m_nDefaultFontSize == 0) + { + SfxItemSet sfxatts = GetAttribs(0, 0, 0, GetAttribsFlags::CHARATTRIBS); + const SvxFontHeightItem* sfxattsh = sfxatts.GetItem(EE_CHAR_FONTHEIGHT); + m_nDefaultFontSize = sfxattsh->GetHeight(); + } + + // Now we calculate the new font size + sal_Int32 nNewFontSize = m_nDefaultFontSize * m_nNewZoom / 100; + + // We apply the new font size to all the text + updateAllESelection(); // Update length of the text + SfxItemSet aSet = GetEmptyItemSet(); + aSet.Put(SvxFontHeightItem(nNewFontSize, 100, EE_CHAR_FONTHEIGHT)); + QuickSetAttribs(aSet, m_aAllSelection); + + // We don't forget to equalize the zoomvalues + m_nOldZoom = m_nNewZoom; +} + +void SmEditEngine::updateAllESelection() +{ + sal_Int32 paracount = GetParagraphCount(); + m_aAllSelection.nEndPara = paracount > 0 ? paracount - 1 : 0; + sal_Int32 textlength = GetTextLen(m_aAllSelection.nEndPara); + m_aAllSelection.nEndPos = textlength > 0 ? textlength : 0; +} + +void SmEditEngine::setSmItemPool(SfxItemPool* mpItemPool, const SvtLinguOptions& maLangOptions) +{ + // Set fonts to be used + struct FontData + { + LanguageType nFallbackLang; + LanguageType nLang; + DefaultFontType nFontType; + sal_uInt16 nFontInfoId; + }; + + FontData aFontDataTable[3] + = { // info to get western font to be used + { LANGUAGE_ENGLISH_US, maLangOptions.nDefaultLanguage, DefaultFontType::FIXED, + EE_CHAR_FONTINFO }, + // info to get CJK font to be used + { LANGUAGE_JAPANESE, maLangOptions.nDefaultLanguage_CJK, DefaultFontType::CJK_TEXT, + EE_CHAR_FONTINFO_CJK }, + // info to get CTL font to be used + { LANGUAGE_ARABIC_SAUDI_ARABIA, maLangOptions.nDefaultLanguage_CTL, + DefaultFontType::CTL_TEXT, EE_CHAR_FONTINFO_CTL } + }; + + // Text color + auto aDefaultDevice = Application::GetDefaultDevice(); + Color aTextColor = aDefaultDevice->GetSettings().GetStyleSettings().GetFieldTextColor(); + for (const FontData& aFontData : aFontDataTable) + { + LanguageType nLang + = (LANGUAGE_NONE == aFontData.nLang) ? aFontData.nFallbackLang : aFontData.nLang; + vcl::Font aFont = OutputDevice::GetDefaultFont(aFontData.nFontType, nLang, + GetDefaultFontFlags::OnlyOne); + aFont.SetColor(aTextColor); + mpItemPool->SetPoolDefaultItem(SvxFontItem(aFont.GetFamilyType(), aFont.GetFamilyName(), + aFont.GetStyleName(), aFont.GetPitch(), + aFont.GetCharSet(), aFontData.nFontInfoId)); + } + + // Set font heights + SvxFontHeightItem aFontHeight( + aDefaultDevice->LogicToPixel(Size(0, 11), MapMode(MapUnit::MapPoint)).Height(), 100, + EE_CHAR_FONTHEIGHT); + mpItemPool->SetPoolDefaultItem(aFontHeight); + aFontHeight.SetWhich(EE_CHAR_FONTHEIGHT_CJK); + mpItemPool->SetPoolDefaultItem(aFontHeight); + aFontHeight.SetWhich(EE_CHAR_FONTHEIGHT_CTL); + mpItemPool->SetPoolDefaultItem(aFontHeight); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/starmath/source/smmod.cxx b/starmath/source/smmod.cxx new file mode 100644 index 0000000000..0ffb6035c1 --- /dev/null +++ b/starmath/source/smmod.cxx @@ -0,0 +1,238 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <o3tl/string_view.hxx> +#include <sfx2/objface.hxx> +#include <svl/whiter.hxx> +#include <sfx2/viewsh.hxx> +#include <svx/svxids.hrc> +#include <vcl/virdev.hxx> +#include <unotools/syslocale.hxx> +#include <smmod.hxx> +#include <cfgitem.hxx> +#include <dialog.hxx> +#include <view.hxx> +#include <smmod.hrc> +#include <starmath.hrc> +#include <svx/modctrl.hxx> +#include <svtools/colorcfg.hxx> + + +#define ShellClass_SmModule +#include <smslots.hxx> + +OUString SmResId(TranslateId aId) +{ + return Translate::get(aId, SM_MOD()->GetResLocale()); +} + +OUString SmLocalizedSymbolData::GetUiSymbolName( std::u16string_view rExportName ) +{ + OUString aRes; + + for (size_t i = 0; i < SAL_N_ELEMENTS(RID_UI_SYMBOL_NAMES); ++i) + { + if (o3tl::equalsAscii(rExportName, RID_UI_SYMBOL_NAMES[i].getId())) + { + aRes = SmResId(RID_UI_SYMBOL_NAMES[i]); + break; + } + } + + return aRes; +} + +OUString SmLocalizedSymbolData::GetExportSymbolName( std::u16string_view rUiName ) +{ + OUString aRes; + + for (size_t i = 0; i < SAL_N_ELEMENTS(RID_UI_SYMBOL_NAMES); ++i) + { + if (rUiName == SmResId(RID_UI_SYMBOL_NAMES[i])) + { + const char *pKey = RID_UI_SYMBOL_NAMES[i].getId(); + aRes = OUString(pKey, strlen(pKey), RTL_TEXTENCODING_UTF8); + break; + } + } + + return aRes; +} + +OUString SmLocalizedSymbolData::GetUiSymbolSetName( std::u16string_view rExportName ) +{ + OUString aRes; + + for (size_t i = 0; i < SAL_N_ELEMENTS(RID_UI_SYMBOLSET_NAMES); ++i) + { + if (o3tl::equalsAscii(rExportName, RID_UI_SYMBOLSET_NAMES[i].getId())) + { + aRes = SmResId(RID_UI_SYMBOLSET_NAMES[i]); + break; + } + } + + return aRes; +} + +OUString SmLocalizedSymbolData::GetExportSymbolSetName( std::u16string_view rUiName ) +{ + OUString aRes; + + for (size_t i = 0; i < SAL_N_ELEMENTS(RID_UI_SYMBOLSET_NAMES); ++i) + { + if (rUiName == SmResId(RID_UI_SYMBOLSET_NAMES[i])) + { + const char *pKey = RID_UI_SYMBOLSET_NAMES[i].getId(); + aRes = OUString(pKey, strlen(pKey), RTL_TEXTENCODING_UTF8); + break; + } + } + + return aRes; +} + +SFX_IMPL_INTERFACE(SmModule, SfxModule) + +void SmModule::InitInterface_Impl() +{ + GetStaticInterface()->RegisterStatusBar(StatusBarId::MathStatusBar); +} + +SmModule::SmModule(SfxObjectFactory* pObjFact) + : SfxModule("sm"_ostr, {pObjFact}) +{ + SetName("StarMath"); + + SvxModifyControl::RegisterControl(SID_DOC_MODIFIED, this); +} + +SmModule::~SmModule() +{ + if (mpColorConfig) + mpColorConfig->RemoveListener(this); + mpVirtualDev.disposeAndClear(); +} + +svtools::ColorConfig & SmModule::GetColorConfig() +{ + if(!mpColorConfig) + { + mpColorConfig.reset(new svtools::ColorConfig); + mpColorConfig->AddListener(this); + } + return *mpColorConfig; +} + +void SmModule::ConfigurationChanged(utl::ConfigurationBroadcaster* pBrdCst, ConfigurationHints) +{ + if (pBrdCst != mpColorConfig.get()) + return; + + SfxViewShell* pViewShell = SfxViewShell::GetFirst(); + while (pViewShell) + { + // FIXME: What if pViewShell is for a different document, + // but OTOH Math is presumably never used through + // LibreOfficeKit, so maybe an irrelevant concern? + if (dynamic_cast<const SmViewShell *>(pViewShell) != nullptr) + pViewShell->GetWindow()->Invalidate(); + pViewShell = SfxViewShell::GetNext(*pViewShell); + } +} + +SmMathConfig * SmModule::GetConfig() +{ + if(!mpConfig) + mpConfig.reset(new SmMathConfig); + return mpConfig.get(); +} + +SmSymbolManager & SmModule::GetSymbolManager() +{ + return GetConfig()->GetSymbolManager(); +} + +const SvtSysLocale& SmModule::GetSysLocale() +{ + if( !moSysLocale ) + moSysLocale.emplace(); + return *moSysLocale; +} + +VirtualDevice &SmModule::GetDefaultVirtualDev() +{ + if (!mpVirtualDev) + { + mpVirtualDev.reset( VclPtr<VirtualDevice>::Create() ); + mpVirtualDev->SetReferenceDevice( VirtualDevice::RefDevMode::MSO1 ); + } + return *mpVirtualDev; +} + +void SmModule::GetState(SfxItemSet &rSet) +{ + SfxWhichIter aIter(rSet); + + for (sal_uInt16 nWh = aIter.FirstWhich(); 0 != nWh; nWh = aIter.NextWhich()) + switch (nWh) + { + case SID_CONFIGEVENT : + rSet.DisableItem(SID_CONFIGEVENT); + break; + } +} + +std::optional<SfxItemSet> SmModule::CreateItemSet( sal_uInt16 nId ) +{ + std::optional<SfxItemSet> pRet; + if(nId == SID_SM_EDITOPTIONS) + { + pRet.emplace( + GetPool(), + svl::Items< //TP_SMPRINT + SID_PRINTTITLE, SID_PRINTZOOM, + SID_NO_RIGHT_SPACES, SID_SAVE_ONLY_USED_SYMBOLS, + SID_AUTO_CLOSE_BRACKETS, SID_SMEDITWINDOWZOOM, + SID_INLINE_EDIT_ENABLE, SID_INLINE_EDIT_ENABLE>); + + GetConfig()->ConfigToItemSet(*pRet); + } + return pRet; +} + +void SmModule::ApplyItemSet( sal_uInt16 nId, const SfxItemSet& rSet ) +{ + if(nId == SID_SM_EDITOPTIONS) + { + GetConfig()->ItemSetToConfig(rSet); + } +} + +std::unique_ptr<SfxTabPage> SmModule::CreateTabPage( sal_uInt16 nId, weld::Container* pPage, weld::DialogController* pController, const SfxItemSet& rSet ) +{ + std::unique_ptr<SfxTabPage> xRet; + if (nId == SID_SM_TP_PRINTOPTIONS) + xRet = SmPrintOptionsTabPage::Create(pPage, pController, rSet); + return xRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/symbol.cxx b/starmath/source/symbol.cxx new file mode 100644 index 0000000000..4bb854f123 --- /dev/null +++ b/starmath/source/symbol.cxx @@ -0,0 +1,309 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <symbol.hxx> +#include <utility.hxx> +#include <cfgitem.hxx> +#include <smmod.hxx> +#include <format.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> + + +SmSym::SmSym() : + m_aUiName(OUString("unknown")), + m_aSetName(OUString("unknown")), + m_cChar('\0'), + m_bPredefined(false) +{ + m_aExportName = m_aUiName; + m_aFace.SetTransparent(true); + m_aFace.SetAlignment(ALIGN_BASELINE); +} + + +SmSym::SmSym(const SmSym& rSymbol) +{ + *this = rSymbol; +} + + +SmSym::SmSym(const OUString& rName, const vcl::Font& rFont, sal_UCS4 cChar, + const OUString& rSet, bool bIsPredefined) +{ + m_aUiName = m_aExportName = rName; + + m_aFace = rFont; + m_aFace.SetTransparent(true); + m_aFace.SetAlignment(ALIGN_BASELINE); + + m_cChar = cChar; + m_aSetName = rSet; + m_bPredefined = bIsPredefined; +} + + +SmSym& SmSym::operator = (const SmSym& rSymbol) +{ + m_aUiName = rSymbol.m_aUiName; + m_aExportName = rSymbol.m_aExportName; + m_cChar = rSymbol.m_cChar; + m_aFace = rSymbol.m_aFace; + m_aSetName = rSymbol.m_aSetName; + m_bPredefined = rSymbol.m_bPredefined; + + SM_MOD()->GetSymbolManager().SetModified(true); + + return *this; +} + + +bool SmSym::IsEqualInUI( const SmSym& rSymbol ) const +{ + return m_aUiName == rSymbol.m_aUiName && + m_aFace == rSymbol.m_aFace && + m_cChar == rSymbol.m_cChar; +} + +const vcl::Font& SmSym::GetFace(const SmFormat* pFormat) const +{ + if (m_aFace.GetFamilyName().isEmpty()) + { + if (!pFormat) + pFormat = &SM_MOD()->GetConfig()->GetStandardFormat(); + return pFormat->GetFont(FNT_VARIABLE); + } + return m_aFace; +} + +/**************************************************************************/ + + +SmSymbolManager::SmSymbolManager() +{ + m_bModified = false; +} + + +SmSymbolManager::SmSymbolManager(const SmSymbolManager& rSymbolSetManager) +{ + m_aSymbols = rSymbolSetManager.m_aSymbols; + m_bModified = true; +} + + +SmSymbolManager::~SmSymbolManager() +{ +} + + +SmSymbolManager& SmSymbolManager::operator = (const SmSymbolManager& rSymbolSetManager) +{ + m_aSymbols = rSymbolSetManager.m_aSymbols; + m_bModified = true; + return *this; +} + +SmSym* SmSymbolManager::GetSymbolByName(std::u16string_view rSymbolName) +{ + SmSym* pRes = GetSymbolByUiName(rSymbolName); + if (!pRes) + pRes = GetSymbolByExportName(rSymbolName); + return pRes; +} + +SmSym *SmSymbolManager::GetSymbolByUiName(std::u16string_view rSymbolName) +{ + OUString aSymbolName(rSymbolName); + SmSym *pRes = nullptr; + SymbolMap_t::iterator aIt( m_aSymbols.find( aSymbolName ) ); + if (aIt != m_aSymbols.end()) + pRes = &aIt->second; + return pRes; +} + +SmSym* SmSymbolManager::GetSymbolByExportName(std::u16string_view rSymbolName) +{ + SmSym* pRes = nullptr; + for (auto& rPair : m_aSymbols) + { + SmSym& rSymbol = rPair.second; + if (rSymbol.GetExportName() == rSymbolName) + { + pRes = &rSymbol; + break; + } + } + return pRes; +} + + +SymbolPtrVec_t SmSymbolManager::GetSymbols() const +{ + SymbolPtrVec_t aRes; + aRes.reserve(m_aSymbols.size()); + for (const auto& rEntry : m_aSymbols) + aRes.push_back( &rEntry.second ); +// OSL_ENSURE( sSymbols.size() == m_aSymbols.size(), "number of symbols mismatch " ); + return aRes; +} + + +bool SmSymbolManager::AddOrReplaceSymbol( const SmSym &rSymbol, bool bForceChange ) +{ + bool bAdded = false; + + const OUString& aSymbolName( rSymbol.GetUiName() ); + if (!aSymbolName.isEmpty() && !rSymbol.GetSymbolSetName().isEmpty()) + { + const SmSym *pFound = GetSymbolByUiName( aSymbolName ); + const bool bSymbolConflict = pFound && !pFound->IsEqualInUI( rSymbol ); + + // avoid having the same symbol name twice but with different symbols in use + if (!pFound || bForceChange) + { + m_aSymbols[ aSymbolName ] = rSymbol; + bAdded = true; + } + else if (bSymbolConflict) + { + // TODO: to solve this a document owned symbol manager would be required ... + SAL_WARN("starmath", "symbol conflict, different symbol with same name found!"); + // symbols in all formulas. A copy of the global one would be needed here + // and then the new symbol has to be forcefully applied. This would keep + // the current formula intact but will leave the set of symbols in the + // global symbol manager somewhat to chance. + } + + OSL_ENSURE( bAdded, "failed to add symbol" ); + if (bAdded) + m_bModified = true; + OSL_ENSURE( bAdded || (pFound && !bSymbolConflict), "AddOrReplaceSymbol: unresolved symbol conflict" ); + } + + return bAdded; +} + + +void SmSymbolManager::RemoveSymbol( const OUString & rSymbolName ) +{ + if (!rSymbolName.isEmpty()) + { + size_t nOldSize = m_aSymbols.size(); + m_aSymbols.erase( rSymbolName ); + m_bModified = nOldSize != m_aSymbols.size(); + } +} + + +std::set< OUString > SmSymbolManager::GetSymbolSetNames() const +{ + std::set< OUString > aRes; + for (const auto& rEntry : m_aSymbols) + aRes.insert( rEntry.second.GetSymbolSetName() ); + return aRes; +} + + +SymbolPtrVec_t SmSymbolManager::GetSymbolSet( std::u16string_view rSymbolSetName ) +{ + SymbolPtrVec_t aRes; + if (!rSymbolSetName.empty()) + { + for (const auto& rEntry : m_aSymbols) + { + if (rEntry.second.GetSymbolSetName() == rSymbolSetName) + aRes.push_back( &rEntry.second ); + } + } + return aRes; +} + + +void SmSymbolManager::Load() +{ + std::vector< SmSym > aSymbols; + SmMathConfig &rCfg = *SM_MOD()->GetConfig(); + rCfg.GetSymbols( aSymbols ); + size_t nSymbolCount = aSymbols.size(); + + m_aSymbols.clear(); + for (size_t i = 0; i < nSymbolCount; ++i) + { + const SmSym &rSym = aSymbols[i]; + OSL_ENSURE( !rSym.GetUiName().isEmpty(), "symbol without name!" ); + if (!rSym.GetUiName().isEmpty()) + AddOrReplaceSymbol( rSym ); + } + m_bModified = true; + + if (0 == nSymbolCount) + { + SAL_WARN("starmath", "no symbol set found"); + m_bModified = false; + } + + // now add a %i... symbol to the 'iGreek' set for every symbol found in the 'Greek' set. + const OUString aGreekSymbolSetName(SmLocalizedSymbolData::GetUiSymbolSetName(u"Greek")); + const SymbolPtrVec_t aGreekSymbols( GetSymbolSet( aGreekSymbolSetName ) ); + OUString aSymbolSetName = "i" + aGreekSymbolSetName; + size_t nSymbols = aGreekSymbols.size(); + for (size_t i = 0; i < nSymbols; ++i) + { + // make the new symbol a copy but with ITALIC_NORMAL, and add it to iGreek + const SmSym &rSym = *aGreekSymbols[i]; + vcl::Font aFont( rSym.GetFace() ); + OSL_ENSURE( aFont.GetItalic() == ITALIC_NONE, "expected Font with ITALIC_NONE, failed." ); + aFont.SetItalic( ITALIC_NORMAL ); + OUString aSymbolName = "i" + rSym.GetUiName(); + SmSym aSymbol( aSymbolName, aFont, rSym.GetCharacter(), + aSymbolSetName, true /*bIsPredefined*/ ); + aSymbol.SetExportName("i" + rSym.GetExportName()); + + AddOrReplaceSymbol( aSymbol ); + } +} + +void SmSymbolManager::Save() +{ + if (!m_bModified) + return; + + SmMathConfig &rCfg = *SM_MOD()->GetConfig(); + + // prepare to skip symbols from iGreek on saving + OUString aSymbolSetName = "i" + + SmLocalizedSymbolData::GetUiSymbolSetName(u"Greek"); + + SymbolPtrVec_t aTmp( GetSymbols() ); + std::vector< SmSym > aSymbols; + for (const SmSym* i : aTmp) + { + // skip symbols from iGreek set since those symbols always get added + // by computational means in SmSymbolManager::Load + if (i->GetSymbolSetName() != aSymbolSetName) + aSymbols.push_back( *i ); + } + rCfg.SetSymbols( aSymbols ); + + m_bModified = false; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/tmpdevice.cxx b/starmath/source/tmpdevice.cxx new file mode 100644 index 0000000000..077977b39d --- /dev/null +++ b/starmath/source/tmpdevice.cxx @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <smmod.hxx> +#include <utility.hxx> + +#include "tmpdevice.hxx" + +#include <svtools/colorcfg.hxx> +#include <sal/log.hxx> + +// SmTmpDevice +// Allows for font and color changes. The original settings will be restored +// in the destructor. +// It's main purpose is to allow for the "const" in the 'OutputDevice' +// argument in the 'Arrange' functions and restore changes made in the 'Draw' +// functions. +// Usually a MapMode of 1/100th mm will be used. + +SmTmpDevice::SmTmpDevice(OutputDevice &rTheDev, bool bUseMap100th_mm) : + rOutDev(rTheDev) +{ + rOutDev.Push(vcl::PushFlags::FONT | vcl::PushFlags::MAPMODE | + vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR | vcl::PushFlags::TEXTCOLOR); + if (bUseMap100th_mm && SmMapUnit() != rOutDev.GetMapMode().GetMapUnit()) + { + SAL_WARN("starmath", "incorrect MapMode?"); + rOutDev.SetMapMode(MapMode(SmMapUnit())); // format for 100% always + } +} + + +Color SmTmpDevice::GetTextColor(const Color& rTextColor) +{ + if (rTextColor == COL_AUTO) + { + Color aConfigFontColor = SM_MOD()->GetColorConfig().GetColorValue(svtools::FONTCOLOR).nColor; + Color aConfigDocColor = SM_MOD()->GetColorConfig().GetColorValue(svtools::DOCCOLOR).nColor; + return rOutDev.GetReadableFontColor(aConfigFontColor, aConfigDocColor); + } + + return rTextColor; +} + + +void SmTmpDevice::SetFont(const vcl::Font &rNewFont) +{ + rOutDev.SetFont(rNewFont); + rOutDev.SetTextColor(GetTextColor(rNewFont.GetColor())); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/tmpdevice.hxx b/starmath/source/tmpdevice.hxx new file mode 100644 index 0000000000..9c15fe0357 --- /dev/null +++ b/starmath/source/tmpdevice.hxx @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <tools/color.hxx> +#include <vcl/outdev.hxx> + +class SmTmpDevice +{ + OutputDevice& rOutDev; + + SmTmpDevice(const SmTmpDevice&) = delete; + SmTmpDevice& operator=(const SmTmpDevice&) = delete; + + Color GetTextColor(const Color& rTextColor); + +public: + SmTmpDevice(OutputDevice& rTheDev, bool bUseMap100th_mm); + ~SmTmpDevice() COVERITY_NOEXCEPT_FALSE { rOutDev.Pop(); } + + void SetFont(const vcl::Font& rNewFont); + + void SetLineColor(const Color& rColor) { rOutDev.SetLineColor(GetTextColor(rColor)); } + void SetFillColor(const Color& rColor) { rOutDev.SetFillColor(GetTextColor(rColor)); } + + operator OutputDevice&() { return rOutDev; } +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/typemap.cxx b/starmath/source/typemap.cxx new file mode 100644 index 0000000000..ba7910747b --- /dev/null +++ b/starmath/source/typemap.cxx @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <config_options.h> + +#include <sfx2/msg.hxx> +#include <sfx2/zoomitem.hxx> +#include <svx/zoomslideritem.hxx> +#include <svl/slstitm.hxx> + +#ifdef DISABLE_DYNLOADING +/* Avoid clash with the ones from svx/source/form/typemap.cxx */ +#define aSfxInt16Item_Impl starmath_source_appl_typemap_aSfxInt16Item_Impl +#endif + +#define SFX_TYPEMAP +#include <smslots.hxx> + +#ifdef DISABLE_DYNLOADING +#undef aSfxInt16Item_Impl +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/unodoc.cxx b/starmath/source/unodoc.cxx new file mode 100644 index 0000000000..3c125077c4 --- /dev/null +++ b/starmath/source/unodoc.cxx @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sfx2/sfxmodelfactory.hxx> + +#include <smdll.hxx> +#include <document.hxx> +#include <com/sun/star/frame/XModel.hpp> +#include <vcl/svapp.hxx> + +using namespace ::com::sun::star; + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +Math_FormulaDocument_get_implementation( + css::uno::XComponentContext* , css::uno::Sequence<css::uno::Any> const& args) +{ + SolarMutexGuard aGuard; + SmGlobals::ensure(); + css::uno::Reference<css::uno::XInterface> xInterface = sfx2::createSfxModelInstance(args, + [](SfxModelFlags _nCreationFlags) + { + SfxObjectShell* pShell = new SmDocShell( _nCreationFlags ); + return pShell->GetModel(); + }); + xInterface->acquire(); + return xInterface.get(); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/unofilter.cxx b/starmath/source/unofilter.cxx new file mode 100644 index 0000000000..0b73175b00 --- /dev/null +++ b/starmath/source/unofilter.cxx @@ -0,0 +1,116 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <unotools/mediadescriptor.hxx> +#include <unotools/ucbstreamhelper.hxx> +#include <sot/storage.hxx> +#include <cppuhelper/supportsservice.hxx> + +#include <document.hxx> +#include "mathtype.hxx" +#include <unomodel.hxx> +#include <comphelper/diagnose_ex.hxx> + +using namespace ::com::sun::star; + +namespace +{ +/// Invokes the MathType importer via UNO. +class MathTypeFilter + : public cppu::WeakImplHelper<document::XFilter, document::XImporter, lang::XServiceInfo> +{ + uno::Reference<lang::XComponent> m_xDstDoc; + +public: + MathTypeFilter(); + + // XFilter + sal_Bool SAL_CALL filter(const uno::Sequence<beans::PropertyValue>& rDescriptor) override; + void SAL_CALL cancel() override; + + // XImporter + void SAL_CALL setTargetDocument(const uno::Reference<lang::XComponent>& xDoc) override; + + // XServiceInfo + OUString SAL_CALL getImplementationName() override; + sal_Bool SAL_CALL supportsService(const OUString& rServiceName) override; + uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override; +}; +} + +MathTypeFilter::MathTypeFilter() = default; + +sal_Bool MathTypeFilter::filter(const uno::Sequence<beans::PropertyValue>& rDescriptor) +{ + bool bSuccess = false; + try + { + utl::MediaDescriptor aMediaDesc(rDescriptor); + aMediaDesc.addInputStream(); + uno::Reference<io::XInputStream> xInputStream; + aMediaDesc[utl::MediaDescriptor::PROP_INPUTSTREAM] >>= xInputStream; + std::unique_ptr<SvStream> pStream(utl::UcbStreamHelper::CreateStream(xInputStream)); + if (pStream) + { + if (SotStorage::IsStorageFile(pStream.get())) + { + tools::SvRef<SotStorage> aStorage(new SotStorage(pStream.get(), false)); + // Is this a MathType Storage? + if (aStorage->IsStream("Equation Native")) + { + if (auto pModel = dynamic_cast<SmModel*>(m_xDstDoc.get())) + { + auto pDocShell = static_cast<SmDocShell*>(pModel->GetObjectShell()); + OUStringBuffer aText(pDocShell->GetText()); + MathType aEquation(aText); + bSuccess = aEquation.Parse(aStorage.get()); + if (bSuccess) + { + pDocShell->SetText(aText.makeStringAndClear()); + pDocShell->Parse(); + } + } + } + } + } + } + catch (const uno::Exception&) + { + DBG_UNHANDLED_EXCEPTION("starmath"); + } + return bSuccess; +} + +void MathTypeFilter::cancel() {} + +void MathTypeFilter::setTargetDocument(const uno::Reference<lang::XComponent>& xDoc) +{ + m_xDstDoc = xDoc; +} + +OUString MathTypeFilter::getImplementationName() { return "com.sun.star.comp.Math.MathTypeFilter"; } + +sal_Bool MathTypeFilter::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +uno::Sequence<OUString> MathTypeFilter::getSupportedServiceNames() +{ + return { "com.sun.star.document.ImportFilter" }; +} + +extern "C" SAL_DLLPUBLIC_EXPORT uno::XInterface* +com_sun_star_comp_Math_MathTypeFilter_get_implementation(uno::XComponentContext* /*pCtx*/, + uno::Sequence<uno::Any> const& /*rSeq*/) +{ + return cppu::acquire(new MathTypeFilter); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/unomodel.cxx b/starmath/source/unomodel.cxx new file mode 100644 index 0000000000..ea12928d3d --- /dev/null +++ b/starmath/source/unomodel.cxx @@ -0,0 +1,1109 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <sal/log.hxx> + +#include <utility> + +#include <o3tl/any.hxx> +#include <sfx2/printer.hxx> +#include <svl/itemprop.hxx> +#include <svl/itemset.hxx> +#include <vcl/svapp.hxx> +#include <unotools/localedatawrapper.hxx> +#include <vcl/settings.hxx> +#include <vcl/print.hxx> +#include <toolkit/awt/vclxdevice.hxx> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/formula/SymbolDescriptor.hpp> +#include <com/sun/star/awt/Size.hpp> +#include <comphelper/propertysetinfo.hxx> +#include <comphelper/propertyvalue.hxx> +#include <comphelper/sequence.hxx> +#include <comphelper/servicehelper.hxx> +#include <cppuhelper/queryinterface.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <editeng/paperinf.hxx> +#include <unotools/moduleoptions.hxx> +#include <tools/mapunit.hxx> +#include <tools/stream.hxx> + +#include <unomodel.hxx> +#include <document.hxx> +#include <view.hxx> +#include <symbol.hxx> +#include <starmath.hrc> +#include <strings.hrc> +#include <smmod.hxx> +#include <cfgitem.hxx> + +using namespace ::cppu; +using namespace ::comphelper; +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::formula; +using namespace ::com::sun::star::view; +using namespace ::com::sun::star::script; + +SmPrintUIOptions::SmPrintUIOptions() +{ + SmModule *pp = SM_MOD(); + SmMathConfig *pConfig = pp->GetConfig(); + SAL_WARN_IF( !pConfig, "starmath", "SmConfig not found" ); + if (!pConfig) + return; + + sal_Int32 nNumProps = 10, nIdx=0; + + // create sequence of print UI options + // (Actually IsIgnoreSpacesRight is a parser option. Without it we need only 8 properties here.) + m_aUIProperties.resize( nNumProps ); + + // load the math PrinterOptions into the custom tab + m_aUIProperties[nIdx].Name = "OptionsUIFile"; + m_aUIProperties[nIdx++].Value <<= OUString("modules/smath/ui/printeroptions.ui"); + + // create Section for formula (results in an extra tab page in dialog) + SvtModuleOptions aOpt; + OUString aAppGroupname( + SmResId( RID_PRINTUIOPT_PRODNAME ). + replaceFirst( "%s", aOpt.GetModuleName( SvtModuleOptions::EModule::MATH ) ) ); + m_aUIProperties[nIdx++].Value = setGroupControlOpt("tabcontrol-page2", aAppGroupname, ".HelpID:vcl:PrintDialog:TabPage:AppPage"); + + // create subgroup for print options + m_aUIProperties[nIdx++].Value = setSubgroupControlOpt("contents", SmResId( RID_PRINTUIOPT_CONTENTS ), OUString()); + + // create a bool option for title row (matches to SID_PRINTTITLE) + m_aUIProperties[nIdx++].Value = setBoolControlOpt("title", SmResId( RID_PRINTUIOPT_TITLE ), + ".HelpID:vcl:PrintDialog:TitleRow:CheckBox", + PRTUIOPT_TITLE_ROW, + pConfig->IsPrintTitle()); + // create a bool option for formula text (matches to SID_PRINTTEXT) + m_aUIProperties[nIdx++].Value = setBoolControlOpt("formulatext", SmResId( RID_PRINTUIOPT_FRMLTXT ), + ".HelpID:vcl:PrintDialog:FormulaText:CheckBox", + PRTUIOPT_FORMULA_TEXT, + pConfig->IsPrintFormulaText()); + // create a bool option for border (matches to SID_PRINTFRAME) + m_aUIProperties[nIdx++].Value = setBoolControlOpt("borders", SmResId( RID_PRINTUIOPT_BORDERS ), + ".HelpID:vcl:PrintDialog:Border:CheckBox", + PRTUIOPT_BORDER, + pConfig->IsPrintFrame()); + + // create subgroup for print format + m_aUIProperties[nIdx++].Value = setSubgroupControlOpt("size", SmResId( RID_PRINTUIOPT_SIZE ), OUString()); + + // create a radio button group for print format (matches to SID_PRINTSIZE) + Sequence< OUString > aChoices{ + SmResId( RID_PRINTUIOPT_ORIGSIZE ), + SmResId( RID_PRINTUIOPT_FITTOPAGE ), + SmResId( RID_PRINTUIOPT_SCALING ) + }; + Sequence< OUString > aHelpIds{ + ".HelpID:vcl:PrintDialog:PrintFormat:RadioButton:0", + ".HelpID:vcl:PrintDialog:PrintFormat:RadioButton:1", + ".HelpID:vcl:PrintDialog:PrintFormat:RadioButton:2" + }; + Sequence< OUString > aWidgetIds{ + "originalsize", + "fittopage", + "scaling" + }; + OUString aPrintFormatProp( PRTUIOPT_PRINT_FORMAT ); + m_aUIProperties[nIdx++].Value = setChoiceRadiosControlOpt(aWidgetIds, OUString(), + aHelpIds, + aPrintFormatProp, + aChoices, static_cast< sal_Int32 >(pConfig->GetPrintSize()) + ); + + // create a numeric box for scale dependent on PrintFormat = "Scaling" (matches to SID_PRINTZOOM) + vcl::PrinterOptionsHelper::UIControlOptions aRangeOpt( aPrintFormatProp, 2, true ); + m_aUIProperties[nIdx++].Value = setRangeControlOpt("scalingspin", OUString(), + ".HelpID:vcl:PrintDialog:PrintScale:NumericField", + PRTUIOPT_PRINT_SCALE, + pConfig->GetPrintZoomFactor(), // initial value + 10, // min value + 1000, // max value + aRangeOpt); + + Sequence aHintNoLayoutPage{ comphelper::makePropertyValue("HintNoLayoutPage", true) }; + m_aUIProperties[nIdx++].Value <<= aHintNoLayoutPage; + + assert(nIdx == nNumProps); +} + + + +namespace { + +enum SmModelPropertyHandles +{ + HANDLE_FORMULA, + HANDLE_FONT_NAME_MATH, + HANDLE_FONT_NAME_VARIABLES, + HANDLE_FONT_NAME_FUNCTIONS, + HANDLE_FONT_NAME_NUMBERS, + HANDLE_FONT_NAME_TEXT, + HANDLE_CUSTOM_FONT_NAME_SERIF, + HANDLE_CUSTOM_FONT_NAME_SANS, + HANDLE_CUSTOM_FONT_NAME_FIXED, + HANDLE_CUSTOM_FONT_FIXED_POSTURE, + HANDLE_CUSTOM_FONT_FIXED_WEIGHT, + HANDLE_CUSTOM_FONT_SANS_POSTURE, + HANDLE_CUSTOM_FONT_SANS_WEIGHT, + HANDLE_CUSTOM_FONT_SERIF_POSTURE, + HANDLE_CUSTOM_FONT_SERIF_WEIGHT, + HANDLE_FONT_MATH_POSTURE, + HANDLE_FONT_MATH_WEIGHT, + HANDLE_FONT_VARIABLES_POSTURE, + HANDLE_FONT_VARIABLES_WEIGHT, + HANDLE_FONT_FUNCTIONS_POSTURE, + HANDLE_FONT_FUNCTIONS_WEIGHT, + HANDLE_FONT_NUMBERS_POSTURE, + HANDLE_FONT_NUMBERS_WEIGHT, + HANDLE_FONT_TEXT_POSTURE, + HANDLE_FONT_TEXT_WEIGHT, + HANDLE_BASE_FONT_HEIGHT, + HANDLE_RELATIVE_FONT_HEIGHT_TEXT, + HANDLE_RELATIVE_FONT_HEIGHT_INDICES, + HANDLE_RELATIVE_FONT_HEIGHT_FUNCTIONS, + HANDLE_RELATIVE_FONT_HEIGHT_OPERATORS, + HANDLE_RELATIVE_FONT_HEIGHT_LIMITS, + HANDLE_IS_TEXT_MODE, + HANDLE_IS_RIGHT_TO_LEFT, + HANDLE_GREEK_CHAR_STYLE, + HANDLE_ALIGNMENT, + HANDLE_RELATIVE_SPACING, + HANDLE_RELATIVE_LINE_SPACING, + HANDLE_RELATIVE_ROOT_SPACING, + HANDLE_RELATIVE_INDEX_SUPERSCRIPT, + HANDLE_RELATIVE_INDEX_SUBSCRIPT, + HANDLE_RELATIVE_FRACTION_NUMERATOR_HEIGHT, + HANDLE_RELATIVE_FRACTION_DENOMINATOR_DEPTH, + HANDLE_RELATIVE_FRACTION_BAR_EXCESS_LENGTH, + HANDLE_RELATIVE_FRACTION_BAR_LINE_WEIGHT, + HANDLE_RELATIVE_UPPER_LIMIT_DISTANCE, + HANDLE_RELATIVE_LOWER_LIMIT_DISTANCE, + HANDLE_RELATIVE_BRACKET_EXCESS_SIZE, + HANDLE_RELATIVE_BRACKET_DISTANCE, + HANDLE_IS_SCALE_ALL_BRACKETS, + HANDLE_RELATIVE_SCALE_BRACKET_EXCESS_SIZE, + HANDLE_RELATIVE_MATRIX_LINE_SPACING, + HANDLE_RELATIVE_MATRIX_COLUMN_SPACING, + HANDLE_RELATIVE_SYMBOL_PRIMARY_HEIGHT, + HANDLE_RELATIVE_SYMBOL_MINIMUM_HEIGHT, + HANDLE_RELATIVE_OPERATOR_EXCESS_SIZE, + HANDLE_RELATIVE_OPERATOR_SPACING, + HANDLE_LEFT_MARGIN, + HANDLE_RIGHT_MARGIN, + HANDLE_TOP_MARGIN, + HANDLE_BOTTOM_MARGIN, + HANDLE_PRINTER_NAME, + HANDLE_PRINTER_SETUP, + HANDLE_SYMBOLS, + HANDLE_SAVE_THUMBNAIL, + HANDLE_USED_SYMBOLS, + HANDLE_BASIC_LIBRARIES, + HANDLE_RUNTIME_UID, + HANDLE_LOAD_READONLY, // Security Options + HANDLE_DIALOG_LIBRARIES, // #i73329# + HANDLE_BASELINE, + HANDLE_INTEROP_GRAB_BAG, + HANDLE_STARMATH_VERSION +}; + +} + +static const rtl::Reference<PropertySetInfo> & lcl_createModelPropertyInfo () +{ + static const PropertyMapEntry aModelPropertyInfoMap[] = + { + { OUString("Alignment") , HANDLE_ALIGNMENT , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0 }, + { OUString("BaseFontHeight") , HANDLE_BASE_FONT_HEIGHT , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0 }, + { OUString("BasicLibraries") , HANDLE_BASIC_LIBRARIES , cppu::UnoType<script::XLibraryContainer>::get(), PropertyAttribute::READONLY, 0 }, + { OUString("BottomMargin") , HANDLE_BOTTOM_MARGIN , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, DIS_BOTTOMSPACE }, + { OUString("CustomFontNameFixed") , HANDLE_CUSTOM_FONT_NAME_FIXED , ::cppu::UnoType<OUString>::get(), PROPERTY_NONE, FNT_FIXED }, + { OUString("CustomFontNameSans") , HANDLE_CUSTOM_FONT_NAME_SANS , ::cppu::UnoType<OUString>::get(), PROPERTY_NONE, FNT_SANS }, + { OUString("CustomFontNameSerif") , HANDLE_CUSTOM_FONT_NAME_SERIF , ::cppu::UnoType<OUString>::get(), PROPERTY_NONE, FNT_SERIF }, + { OUString("DialogLibraries") , HANDLE_DIALOG_LIBRARIES , cppu::UnoType<script::XLibraryContainer>::get(), PropertyAttribute::READONLY, 0 }, + { OUString("FontFixedIsBold") , HANDLE_CUSTOM_FONT_FIXED_WEIGHT , cppu::UnoType<bool>::get(), PROPERTY_NONE, FNT_FIXED }, + { OUString("FontFixedIsItalic") , HANDLE_CUSTOM_FONT_FIXED_POSTURE , cppu::UnoType<bool>::get(), PROPERTY_NONE, FNT_FIXED }, + { OUString("FontFunctionsIsBold") , HANDLE_FONT_FUNCTIONS_WEIGHT , cppu::UnoType<bool>::get(), PROPERTY_NONE, FNT_FUNCTION }, + { OUString("FontFunctionsIsItalic") , HANDLE_FONT_FUNCTIONS_POSTURE , cppu::UnoType<bool>::get(), PROPERTY_NONE, FNT_FUNCTION }, + { OUString("FontMathIsBold") , HANDLE_FONT_MATH_WEIGHT , cppu::UnoType<bool>::get(), PROPERTY_NONE, FNT_MATH }, + { OUString("FontMathIsItalic") , HANDLE_FONT_MATH_POSTURE , cppu::UnoType<bool>::get(), PROPERTY_NONE, FNT_MATH }, + { OUString("FontNameFunctions") , HANDLE_FONT_NAME_FUNCTIONS , ::cppu::UnoType<OUString>::get(), PROPERTY_NONE, FNT_FUNCTION }, + { OUString("FontNameMath") , HANDLE_FONT_NAME_MATH , ::cppu::UnoType<OUString>::get(), PROPERTY_NONE, FNT_MATH }, + { OUString("FontNameNumbers") , HANDLE_FONT_NAME_NUMBERS , ::cppu::UnoType<OUString>::get(), PROPERTY_NONE, FNT_NUMBER }, + { OUString("FontNameText") , HANDLE_FONT_NAME_TEXT , ::cppu::UnoType<OUString>::get(), PROPERTY_NONE, FNT_TEXT }, + { OUString("FontNameVariables") , HANDLE_FONT_NAME_VARIABLES , ::cppu::UnoType<OUString>::get(), PROPERTY_NONE, FNT_VARIABLE }, + { OUString("FontNumbersIsBold") , HANDLE_FONT_NUMBERS_WEIGHT , cppu::UnoType<bool>::get(), PROPERTY_NONE, FNT_NUMBER }, + { OUString("FontNumbersIsItalic") , HANDLE_FONT_NUMBERS_POSTURE , cppu::UnoType<bool>::get(), PROPERTY_NONE, FNT_NUMBER }, + { OUString("FontSansIsBold") , HANDLE_CUSTOM_FONT_SANS_WEIGHT , cppu::UnoType<bool>::get(), PROPERTY_NONE, FNT_SANS }, + { OUString("FontSansIsItalic") , HANDLE_CUSTOM_FONT_SANS_POSTURE , cppu::UnoType<bool>::get(), PROPERTY_NONE, FNT_SANS }, + { OUString("FontSerifIsBold") , HANDLE_CUSTOM_FONT_SERIF_WEIGHT , cppu::UnoType<bool>::get(), PROPERTY_NONE, FNT_SERIF }, + { OUString("FontSerifIsItalic") , HANDLE_CUSTOM_FONT_SERIF_POSTURE , cppu::UnoType<bool>::get(), PROPERTY_NONE, FNT_SERIF }, + { OUString("FontTextIsBold") , HANDLE_FONT_TEXT_WEIGHT , cppu::UnoType<bool>::get(), PROPERTY_NONE, FNT_TEXT }, + { OUString("FontTextIsItalic") , HANDLE_FONT_TEXT_POSTURE , cppu::UnoType<bool>::get(), PROPERTY_NONE, FNT_TEXT }, + { OUString("FontVariablesIsBold") , HANDLE_FONT_VARIABLES_WEIGHT , cppu::UnoType<bool>::get(), PROPERTY_NONE, FNT_VARIABLE }, + { OUString("FontVariablesIsItalic") , HANDLE_FONT_VARIABLES_POSTURE , cppu::UnoType<bool>::get(), PROPERTY_NONE, FNT_VARIABLE }, + { OUString("Formula") , HANDLE_FORMULA , ::cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0 }, + { OUString("IsScaleAllBrackets") , HANDLE_IS_SCALE_ALL_BRACKETS , cppu::UnoType<bool>::get(), PROPERTY_NONE, 0 }, + { OUString("IsTextMode") , HANDLE_IS_TEXT_MODE , cppu::UnoType<bool>::get(), PROPERTY_NONE, 0 }, + { OUString("IsRightToLeft") , HANDLE_IS_RIGHT_TO_LEFT , cppu::UnoType<bool>::get(), PROPERTY_NONE, 0 }, + { OUString("GreekCharStyle") , HANDLE_GREEK_CHAR_STYLE , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0 }, + { OUString("LeftMargin") , HANDLE_LEFT_MARGIN , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, DIS_LEFTSPACE }, + { OUString("PrinterName") , HANDLE_PRINTER_NAME , ::cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0 }, + { OUString("PrinterSetup") , HANDLE_PRINTER_SETUP , cppu::UnoType<const Sequence < sal_Int8 >>::get(), PROPERTY_NONE, 0 }, + { OUString("RelativeBracketDistance") , HANDLE_RELATIVE_BRACKET_DISTANCE , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, DIS_BRACKETSPACE }, + { OUString("RelativeBracketExcessSize") , HANDLE_RELATIVE_BRACKET_EXCESS_SIZE , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, DIS_BRACKETSIZE }, + { OUString("RelativeFontHeightFunctions") , HANDLE_RELATIVE_FONT_HEIGHT_FUNCTIONS , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, SIZ_FUNCTION }, + { OUString("RelativeFontHeightIndices") , HANDLE_RELATIVE_FONT_HEIGHT_INDICES , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, SIZ_INDEX }, + { OUString("RelativeFontHeightLimits") , HANDLE_RELATIVE_FONT_HEIGHT_LIMITS , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, SIZ_LIMITS }, + { OUString("RelativeFontHeightOperators") , HANDLE_RELATIVE_FONT_HEIGHT_OPERATORS , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, SIZ_OPERATOR }, + { OUString("RelativeFontHeightText") , HANDLE_RELATIVE_FONT_HEIGHT_TEXT , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, SIZ_TEXT }, + { OUString("RelativeFractionBarExcessLength") , HANDLE_RELATIVE_FRACTION_BAR_EXCESS_LENGTH, ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, DIS_FRACTION }, + { OUString("RelativeFractionBarLineWeight") , HANDLE_RELATIVE_FRACTION_BAR_LINE_WEIGHT , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, DIS_STROKEWIDTH }, + { OUString("RelativeFractionDenominatorDepth") , HANDLE_RELATIVE_FRACTION_DENOMINATOR_DEPTH, ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, DIS_DENOMINATOR }, + { OUString("RelativeFractionNumeratorHeight") , HANDLE_RELATIVE_FRACTION_NUMERATOR_HEIGHT , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, DIS_NUMERATOR }, + { OUString("RelativeIndexSubscript") , HANDLE_RELATIVE_INDEX_SUBSCRIPT , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, DIS_SUBSCRIPT }, + { OUString("RelativeIndexSuperscript") , HANDLE_RELATIVE_INDEX_SUPERSCRIPT , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, DIS_SUPERSCRIPT }, + { OUString("RelativeLineSpacing") , HANDLE_RELATIVE_LINE_SPACING , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, DIS_VERTICAL }, + { OUString("RelativeLowerLimitDistance") , HANDLE_RELATIVE_LOWER_LIMIT_DISTANCE , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, DIS_LOWERLIMIT }, + { OUString("RelativeMatrixColumnSpacing") , HANDLE_RELATIVE_MATRIX_COLUMN_SPACING , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, DIS_MATRIXCOL }, + { OUString("RelativeMatrixLineSpacing") , HANDLE_RELATIVE_MATRIX_LINE_SPACING , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, DIS_MATRIXROW }, + { OUString("RelativeOperatorExcessSize") , HANDLE_RELATIVE_OPERATOR_EXCESS_SIZE , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, DIS_OPERATORSIZE }, + { OUString("RelativeOperatorSpacing") , HANDLE_RELATIVE_OPERATOR_SPACING , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, DIS_OPERATORSPACE }, + { OUString("RelativeRootSpacing") , HANDLE_RELATIVE_ROOT_SPACING , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, DIS_ROOT }, + { OUString("RelativeScaleBracketExcessSize") , HANDLE_RELATIVE_SCALE_BRACKET_EXCESS_SIZE , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, DIS_NORMALBRACKETSIZE }, + { OUString("RelativeSpacing") , HANDLE_RELATIVE_SPACING , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, DIS_HORIZONTAL }, + { OUString("RelativeSymbolMinimumHeight") , HANDLE_RELATIVE_SYMBOL_MINIMUM_HEIGHT , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, DIS_ORNAMENTSPACE }, + { OUString("RelativeSymbolPrimaryHeight") , HANDLE_RELATIVE_SYMBOL_PRIMARY_HEIGHT , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, DIS_ORNAMENTSIZE }, + { OUString("RelativeUpperLimitDistance") , HANDLE_RELATIVE_UPPER_LIMIT_DISTANCE , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, DIS_UPPERLIMIT }, + { OUString("RightMargin") , HANDLE_RIGHT_MARGIN , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, DIS_RIGHTSPACE }, + { OUString("RuntimeUID") , HANDLE_RUNTIME_UID , cppu::UnoType<OUString>::get(), PropertyAttribute::READONLY, 0 }, + { OUString("SaveThumbnail") , HANDLE_SAVE_THUMBNAIL , cppu::UnoType<bool>::get(), PROPERTY_NONE, 0 }, + { OUString("Symbols") , HANDLE_SYMBOLS , cppu::UnoType<Sequence < SymbolDescriptor >>::get(), PROPERTY_NONE, 0 }, + { OUString("UserDefinedSymbolsInUse") , HANDLE_USED_SYMBOLS , cppu::UnoType<Sequence < SymbolDescriptor >>::get(), PropertyAttribute::READONLY, 0 }, + { OUString("TopMargin") , HANDLE_TOP_MARGIN , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, DIS_TOPSPACE }, + // #i33095# Security Options + { OUString("LoadReadonly") , HANDLE_LOAD_READONLY , cppu::UnoType<bool>::get(), PROPERTY_NONE, 0 }, + // #i972# + { OUString("BaseLine") , HANDLE_BASELINE , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0 }, + { OUString("InteropGrabBag") , HANDLE_INTEROP_GRAB_BAG , cppu::UnoType<uno::Sequence< beans::PropertyValue >>::get(), PROPERTY_NONE, 0 }, + { OUString("SyntaxVersion") , HANDLE_STARMATH_VERSION , ::cppu::UnoType<sal_Int16>::get(), PROPERTY_NONE, 0 }, + }; + static const rtl::Reference<PropertySetInfo> PROPS_INFO = new PropertySetInfo ( aModelPropertyInfoMap ); + return PROPS_INFO; +} + +SmModel::SmModel( SfxObjectShell *pObjSh ) +: SfxBaseModel(pObjSh) +, PropertySetHelper ( lcl_createModelPropertyInfo () ) +{ +} + +SmModel::~SmModel() noexcept +{ +} + +uno::Any SAL_CALL SmModel::queryInterface( const uno::Type& rType ) +{ + uno::Any aRet = ::cppu::queryInterface ( rType, + // PropertySetHelper interfaces + static_cast< XPropertySet* > ( this ), + static_cast< XMultiPropertySet* > ( this ), + // my own interfaces + static_cast< XServiceInfo* > ( this ), + static_cast< XRenderable* > ( this ) ); + if (!aRet.hasValue()) + aRet = SfxBaseModel::queryInterface ( rType ); + return aRet; +} + +void SAL_CALL SmModel::acquire() noexcept +{ + OWeakObject::acquire(); +} + +void SAL_CALL SmModel::release() noexcept +{ + OWeakObject::release(); +} + +uno::Sequence< uno::Type > SAL_CALL SmModel::getTypes( ) +{ + return comphelper::concatSequences(SfxBaseModel::getTypes(), + uno::Sequence { + cppu::UnoType<XServiceInfo>::get(), + cppu::UnoType<XPropertySet>::get(), + cppu::UnoType<XMultiPropertySet>::get(), + cppu::UnoType<XRenderable>::get() }); +} + +const uno::Sequence< sal_Int8 > & SmModel::getUnoTunnelId() +{ + static const comphelper::UnoIdInit theSmModelUnoTunnelId; + return theSmModelUnoTunnelId.getSeq(); +} + +sal_Int64 SAL_CALL SmModel::getSomething( const uno::Sequence< sal_Int8 >& rId ) +{ + return comphelper::getSomethingImpl(rId, this, + comphelper::FallbackToGetSomethingOf<SfxBaseModel>{}); +} + +static sal_Int16 lcl_AnyToINT16(const uno::Any& rAny) +{ + sal_Int16 nRet = 0; + if( auto x = o3tl::tryAccess<double>(rAny) ) + nRet = static_cast<sal_Int16>(*x); + else + rAny >>= nRet; + return nRet; +} + +OUString SmModel::getImplementationName() +{ + return "com.sun.star.comp.Math.FormulaDocument"; +} + +sal_Bool SmModel::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +uno::Sequence< OUString > SmModel::getSupportedServiceNames() +{ + static constexpr OUString service1 = u"com.sun.star.document.OfficeDocument"_ustr; + static constexpr OUString service2 = u"com.sun.star.formula.FormulaProperties"_ustr; + return uno::Sequence<OUString>{ service1, service2 }; +} + +void SmModel::_setPropertyValues(const PropertyMapEntry** ppEntries, const Any* pValues) +{ + SolarMutexGuard aGuard; + + SmDocShell *pDocSh = static_cast < SmDocShell * > (GetObjectShell()); + + if ( nullptr == pDocSh ) + throw UnknownPropertyException(); + + SmFormat aFormat = pDocSh->GetFormat(); + + for (; *ppEntries; ppEntries++, pValues++ ) + { + if ((*ppEntries)->mnAttributes & PropertyAttribute::READONLY) + throw PropertyVetoException(); + + switch ( (*ppEntries)->mnHandle ) + { + case HANDLE_FORMULA: + { + OUString aText; + *pValues >>= aText; + pDocSh->SetText(aText); + } + break; + case HANDLE_FONT_NAME_MATH : + case HANDLE_FONT_NAME_VARIABLES : + case HANDLE_FONT_NAME_FUNCTIONS : + case HANDLE_FONT_NAME_NUMBERS : + case HANDLE_FONT_NAME_TEXT : + case HANDLE_CUSTOM_FONT_NAME_SERIF : + case HANDLE_CUSTOM_FONT_NAME_SANS : + case HANDLE_CUSTOM_FONT_NAME_FIXED : + { + OUString sFontName; + *pValues >>= sFontName; + if(sFontName.isEmpty()) + throw IllegalArgumentException(); + maFonts[(*ppEntries)->mnMemberId].SetFamilyName(sFontName); + } + break; + case HANDLE_CUSTOM_FONT_FIXED_POSTURE: + case HANDLE_CUSTOM_FONT_SANS_POSTURE : + case HANDLE_CUSTOM_FONT_SERIF_POSTURE: + case HANDLE_FONT_MATH_POSTURE : + case HANDLE_FONT_VARIABLES_POSTURE : + case HANDLE_FONT_FUNCTIONS_POSTURE : + case HANDLE_FONT_NUMBERS_POSTURE : + case HANDLE_FONT_TEXT_POSTURE : + { + std::optional<const bool> bVal = o3tl::tryAccess<bool>(*pValues); + if(!bVal.has_value()) + throw IllegalArgumentException(); + maFonts[(*ppEntries)->mnMemberId].SetItalic(*bVal ? ITALIC_NORMAL : ITALIC_NONE); + } + break; + case HANDLE_CUSTOM_FONT_FIXED_WEIGHT : + case HANDLE_CUSTOM_FONT_SANS_WEIGHT : + case HANDLE_CUSTOM_FONT_SERIF_WEIGHT : + case HANDLE_FONT_MATH_WEIGHT : + case HANDLE_FONT_VARIABLES_WEIGHT : + case HANDLE_FONT_FUNCTIONS_WEIGHT : + case HANDLE_FONT_NUMBERS_WEIGHT : + case HANDLE_FONT_TEXT_WEIGHT : + { + std::optional<const bool> bVal = o3tl::tryAccess<bool>(*pValues); + if(!bVal.has_value()) + throw IllegalArgumentException(); + maFonts[(*ppEntries)->mnMemberId].SetWeight(*bVal ? WEIGHT_BOLD : WEIGHT_NORMAL); + } + break; + case HANDLE_BASE_FONT_HEIGHT : + { + // Point! + sal_Int16 nVal = lcl_AnyToINT16(*pValues); + if(nVal < 1) + throw IllegalArgumentException(); + Size aSize = aFormat.GetBaseSize(); + aSize.setHeight(o3tl::convert(nVal, o3tl::Length::pt, SmO3tlLengthUnit())); + aFormat.SetBaseSize(aSize); + + // apply base size to fonts + const Size aTmp( aFormat.GetBaseSize() ); + for (sal_uInt16 i = FNT_BEGIN; i <= FNT_END; i++) + maFonts[i].SetSize(aTmp); + } + break; + case HANDLE_RELATIVE_FONT_HEIGHT_TEXT : + case HANDLE_RELATIVE_FONT_HEIGHT_INDICES : + case HANDLE_RELATIVE_FONT_HEIGHT_FUNCTIONS : + case HANDLE_RELATIVE_FONT_HEIGHT_OPERATORS : + case HANDLE_RELATIVE_FONT_HEIGHT_LIMITS : + { + sal_Int16 nVal = 0; + *pValues >>= nVal; + if(nVal < 1) + throw IllegalArgumentException(); + aFormat.SetRelSize((*ppEntries)->mnMemberId, nVal); + } + break; + + case HANDLE_IS_TEXT_MODE : + { + aFormat.SetTextmode(*o3tl::doAccess<bool>(*pValues)); + } + break; + + case HANDLE_IS_RIGHT_TO_LEFT : + aFormat.SetRightToLeft(*o3tl::doAccess<bool>(*pValues)); + break; + + case HANDLE_GREEK_CHAR_STYLE : + { + sal_Int16 nVal = 0; + *pValues >>= nVal; + if (nVal < 0 || nVal > 2) + throw IllegalArgumentException(); + aFormat.SetGreekCharStyle( nVal ); + } + break; + + case HANDLE_ALIGNMENT : + { + // SmHorAlign uses the same values as HorizontalAlignment + sal_Int16 nVal = 0; + *pValues >>= nVal; + if(nVal < 0 || nVal > 2) + throw IllegalArgumentException(); + aFormat.SetHorAlign(static_cast<SmHorAlign>(nVal)); + } + break; + + case HANDLE_RELATIVE_SPACING : + case HANDLE_RELATIVE_LINE_SPACING : + case HANDLE_RELATIVE_ROOT_SPACING : + case HANDLE_RELATIVE_INDEX_SUPERSCRIPT : + case HANDLE_RELATIVE_INDEX_SUBSCRIPT : + case HANDLE_RELATIVE_FRACTION_NUMERATOR_HEIGHT : + case HANDLE_RELATIVE_FRACTION_DENOMINATOR_DEPTH: + case HANDLE_RELATIVE_FRACTION_BAR_EXCESS_LENGTH: + case HANDLE_RELATIVE_FRACTION_BAR_LINE_WEIGHT : + case HANDLE_RELATIVE_UPPER_LIMIT_DISTANCE : + case HANDLE_RELATIVE_LOWER_LIMIT_DISTANCE : + case HANDLE_RELATIVE_BRACKET_EXCESS_SIZE : + case HANDLE_RELATIVE_BRACKET_DISTANCE : + case HANDLE_RELATIVE_SCALE_BRACKET_EXCESS_SIZE : + case HANDLE_RELATIVE_MATRIX_LINE_SPACING : + case HANDLE_RELATIVE_MATRIX_COLUMN_SPACING : + case HANDLE_RELATIVE_SYMBOL_PRIMARY_HEIGHT : + case HANDLE_RELATIVE_SYMBOL_MINIMUM_HEIGHT : + case HANDLE_RELATIVE_OPERATOR_EXCESS_SIZE : + case HANDLE_RELATIVE_OPERATOR_SPACING : + case HANDLE_LEFT_MARGIN : + case HANDLE_RIGHT_MARGIN : + case HANDLE_TOP_MARGIN : + case HANDLE_BOTTOM_MARGIN : + { + sal_Int16 nVal = 0; + *pValues >>= nVal; + if(nVal < 0) + throw IllegalArgumentException(); + aFormat.SetDistance((*ppEntries)->mnMemberId, nVal); + } + break; + case HANDLE_IS_SCALE_ALL_BRACKETS : + aFormat.SetScaleNormalBrackets(*o3tl::doAccess<bool>(*pValues)); + break; + case HANDLE_PRINTER_NAME: + { + // embedded documents just ignore this property for now + if ( pDocSh->GetCreateMode() != SfxObjectCreateMode::EMBEDDED ) + { + SfxPrinter *pPrinter = pDocSh->GetPrinter ( ); + if (pPrinter) + { + OUString sPrinterName; + if ( !(*pValues >>= sPrinterName) ) + throw IllegalArgumentException(); + + if ( !sPrinterName.isEmpty() ) + { + VclPtrInstance<SfxPrinter> pNewPrinter( pPrinter->GetOptions().Clone(), sPrinterName ); + if (pNewPrinter->IsKnown()) + pDocSh->SetPrinter ( pNewPrinter ); + else + pNewPrinter.disposeAndClear(); + } + } + } + } + break; + case HANDLE_PRINTER_SETUP: + { + Sequence < sal_Int8 > aSequence; + if ( !(*pValues >>= aSequence) ) + throw IllegalArgumentException(); + + sal_uInt32 nSize = aSequence.getLength(); + SvMemoryStream aStream ( aSequence.getArray(), nSize, StreamMode::READ ); + aStream.Seek ( STREAM_SEEK_TO_BEGIN ); + auto pItemSet = std::make_unique<SfxItemSetFixed< + SID_PRINTTITLE, SID_PRINTTITLE, + SID_PRINTTEXT, SID_PRINTTEXT, + SID_PRINTFRAME, SID_PRINTFRAME, + SID_PRINTSIZE, SID_PRINTSIZE, + SID_PRINTZOOM, SID_PRINTZOOM, + SID_NO_RIGHT_SPACES, SID_NO_RIGHT_SPACES, + SID_SAVE_ONLY_USED_SYMBOLS, SID_SAVE_ONLY_USED_SYMBOLS, + SID_AUTO_CLOSE_BRACKETS, SID_SMEDITWINDOWZOOM, + SID_INLINE_EDIT_ENABLE, SID_INLINE_EDIT_ENABLE>> ( SmDocShell::GetPool() ); + SmModule *pp = SM_MOD(); + pp->GetConfig()->ConfigToItemSet(*pItemSet); + VclPtr<SfxPrinter> pPrinter = SfxPrinter::Create ( aStream, std::move(pItemSet) ); + + pDocSh->SetPrinter( pPrinter ); + } + break; + case HANDLE_SYMBOLS: + { + // this is set + Sequence < SymbolDescriptor > aSequence; + if ( !(*pValues >>= aSequence) ) + throw IllegalArgumentException(); + + SmModule *pp = SM_MOD(); + SmSymbolManager &rManager = pp->GetSymbolManager(); + for (const SymbolDescriptor& rDescriptor : std::as_const(aSequence)) + { + vcl::Font aFont; + aFont.SetFamilyName ( rDescriptor.sFontName ); + aFont.SetCharSet ( static_cast < rtl_TextEncoding > (rDescriptor.nCharSet) ); + aFont.SetFamily ( static_cast < FontFamily > (rDescriptor.nFamily ) ); + aFont.SetPitch ( static_cast < FontPitch > (rDescriptor.nPitch ) ); + aFont.SetWeight ( static_cast < FontWeight > (rDescriptor.nWeight ) ); + aFont.SetItalic ( static_cast < FontItalic > (rDescriptor.nItalic ) ); + SmSym aSymbol ( rDescriptor.sName, aFont, static_cast < sal_Unicode > (rDescriptor.nCharacter), + rDescriptor.sSymbolSet ); + aSymbol.SetExportName ( rDescriptor.sExportName ); + rManager.AddOrReplaceSymbol ( aSymbol ); + } + } + break; + // #i33095# Security Options + case HANDLE_LOAD_READONLY : + { + if ( (*pValues).getValueType() != cppu::UnoType<bool>::get() ) + throw IllegalArgumentException(); + bool bReadonly = false; + if ( *pValues >>= bReadonly ) + pDocSh->SetLoadReadonly( bReadonly ); + break; + } + case HANDLE_INTEROP_GRAB_BAG: + setGrabBagItem(*pValues); + break; + case HANDLE_SAVE_THUMBNAIL: + { + if ((*pValues).getValueType() != cppu::UnoType<bool>::get()) + throw IllegalArgumentException(); + bool bThumbnail = false; + if (*pValues >>= bThumbnail) + pDocSh->SetUseThumbnailSave(bThumbnail); + } + break; + case HANDLE_STARMATH_VERSION: + pDocSh->SetSmSyntaxVersion(pValues->get<sal_Int16>()); + break; + } + } + + // tdf#143213 + // Collect all font settings and apply them at the end, since the font name change can be seen + // after italic or bold settings and would then override them. + for (sal_uInt16 nFontDesc = FNT_BEGIN; nFontDesc <= FNT_END; ++nFontDesc) + { + const SmFace& rFont = maFonts[nFontDesc]; + if (rFont.GetFamilyName().isEmpty()) + continue; + + if (aFormat.GetFont(nFontDesc).GetFamilyName() != rFont.GetFamilyName()) + { + const SmFace rOld = aFormat.GetFont(nFontDesc); + + SmFace aSet(rFont.GetFamilyName(), rOld.GetFontSize()); + aSet.SetBorderWidth(rOld.GetBorderWidth()); + aSet.SetAlignment(ALIGN_BASELINE); + aFormat.SetFont(nFontDesc, aSet); + } + + if (aFormat.GetFont(nFontDesc).GetItalic() != rFont.GetItalic()) + { + vcl::Font aNewFont(aFormat.GetFont(nFontDesc)); + aNewFont.SetItalic(rFont.GetItalic()); + aFormat.SetFont(nFontDesc, aNewFont); + } + + if (aFormat.GetFont(nFontDesc).GetWeight() != rFont.GetWeight()) + { + vcl::Font aNewFont(aFormat.GetFont(nFontDesc)); + aNewFont.SetWeight(rFont.GetWeight()); + aFormat.SetFont(nFontDesc, aNewFont); + } + + if (aFormat.GetFont(nFontDesc).GetFontSize() != rFont.GetFontSize()) + { + aFormat.SetFontSize(nFontDesc, rFont.GetFontSize()); + } + } + + pDocSh->SetFormat( aFormat ); + + // #i67283# since about all of the above changes are likely to change + // the formula size we have to recalculate the vis-area now + pDocSh->SetVisArea( tools::Rectangle( Point(0, 0), pDocSh->GetSize() ) ); +} + +void SmModel::_getPropertyValues( const PropertyMapEntry **ppEntries, Any *pValue ) +{ + SmDocShell *pDocSh = static_cast < SmDocShell * > (GetObjectShell()); + + if ( nullptr == pDocSh ) + throw UnknownPropertyException(); + + const SmFormat & aFormat = pDocSh->GetFormat(); + + for (; *ppEntries; ppEntries++, pValue++ ) + { + switch ( (*ppEntries)->mnHandle ) + { + case HANDLE_FORMULA: + *pValue <<= pDocSh->GetText(); + break; + case HANDLE_FONT_NAME_MATH : + case HANDLE_FONT_NAME_VARIABLES : + case HANDLE_FONT_NAME_FUNCTIONS : + case HANDLE_FONT_NAME_NUMBERS : + case HANDLE_FONT_NAME_TEXT : + case HANDLE_CUSTOM_FONT_NAME_SERIF : + case HANDLE_CUSTOM_FONT_NAME_SANS : + case HANDLE_CUSTOM_FONT_NAME_FIXED : + { + const SmFace & rFace = aFormat.GetFont((*ppEntries)->mnMemberId); + *pValue <<= rFace.GetFamilyName(); + } + break; + case HANDLE_CUSTOM_FONT_FIXED_POSTURE: + case HANDLE_CUSTOM_FONT_SANS_POSTURE : + case HANDLE_CUSTOM_FONT_SERIF_POSTURE: + case HANDLE_FONT_MATH_POSTURE : + case HANDLE_FONT_VARIABLES_POSTURE : + case HANDLE_FONT_FUNCTIONS_POSTURE : + case HANDLE_FONT_NUMBERS_POSTURE : + case HANDLE_FONT_TEXT_POSTURE : + { + const SmFace & rFace = aFormat.GetFont((*ppEntries)->mnMemberId); + *pValue <<= IsItalic( rFace ); + } + break; + case HANDLE_CUSTOM_FONT_FIXED_WEIGHT : + case HANDLE_CUSTOM_FONT_SANS_WEIGHT : + case HANDLE_CUSTOM_FONT_SERIF_WEIGHT : + case HANDLE_FONT_MATH_WEIGHT : + case HANDLE_FONT_VARIABLES_WEIGHT : + case HANDLE_FONT_FUNCTIONS_WEIGHT : + case HANDLE_FONT_NUMBERS_WEIGHT : + case HANDLE_FONT_TEXT_WEIGHT : + { + const SmFace & rFace = aFormat.GetFont((*ppEntries)->mnMemberId); + *pValue <<= IsBold( rFace ); + } + break; + case HANDLE_BASE_FONT_HEIGHT : + { + // Point! + *pValue <<= sal_Int16(o3tl::convert(aFormat.GetBaseSize().Height(), + SmO3tlLengthUnit(), o3tl::Length::pt)); + } + break; + case HANDLE_RELATIVE_FONT_HEIGHT_TEXT : + case HANDLE_RELATIVE_FONT_HEIGHT_INDICES : + case HANDLE_RELATIVE_FONT_HEIGHT_FUNCTIONS : + case HANDLE_RELATIVE_FONT_HEIGHT_OPERATORS : + case HANDLE_RELATIVE_FONT_HEIGHT_LIMITS : + *pValue <<= static_cast<sal_Int16>(aFormat.GetRelSize((*ppEntries)->mnMemberId)); + break; + + case HANDLE_IS_TEXT_MODE : + *pValue <<= aFormat.IsTextmode(); + break; + + case HANDLE_IS_RIGHT_TO_LEFT : + *pValue <<= aFormat.IsRightToLeft(); + break; + + case HANDLE_GREEK_CHAR_STYLE : + *pValue <<= aFormat.GetGreekCharStyle(); + break; + + case HANDLE_ALIGNMENT : + // SmHorAlign uses the same values as HorizontalAlignment + *pValue <<= static_cast<sal_Int16>(aFormat.GetHorAlign()); + break; + + case HANDLE_RELATIVE_SPACING : + case HANDLE_RELATIVE_LINE_SPACING : + case HANDLE_RELATIVE_ROOT_SPACING : + case HANDLE_RELATIVE_INDEX_SUPERSCRIPT : + case HANDLE_RELATIVE_INDEX_SUBSCRIPT : + case HANDLE_RELATIVE_FRACTION_NUMERATOR_HEIGHT : + case HANDLE_RELATIVE_FRACTION_DENOMINATOR_DEPTH: + case HANDLE_RELATIVE_FRACTION_BAR_EXCESS_LENGTH: + case HANDLE_RELATIVE_FRACTION_BAR_LINE_WEIGHT : + case HANDLE_RELATIVE_UPPER_LIMIT_DISTANCE : + case HANDLE_RELATIVE_LOWER_LIMIT_DISTANCE : + case HANDLE_RELATIVE_BRACKET_EXCESS_SIZE : + case HANDLE_RELATIVE_BRACKET_DISTANCE : + case HANDLE_RELATIVE_SCALE_BRACKET_EXCESS_SIZE : + case HANDLE_RELATIVE_MATRIX_LINE_SPACING : + case HANDLE_RELATIVE_MATRIX_COLUMN_SPACING : + case HANDLE_RELATIVE_SYMBOL_PRIMARY_HEIGHT : + case HANDLE_RELATIVE_SYMBOL_MINIMUM_HEIGHT : + case HANDLE_RELATIVE_OPERATOR_EXCESS_SIZE : + case HANDLE_RELATIVE_OPERATOR_SPACING : + case HANDLE_LEFT_MARGIN : + case HANDLE_RIGHT_MARGIN : + case HANDLE_TOP_MARGIN : + case HANDLE_BOTTOM_MARGIN : + *pValue <<= static_cast<sal_Int16>(aFormat.GetDistance((*ppEntries)->mnMemberId)); + break; + case HANDLE_IS_SCALE_ALL_BRACKETS : + *pValue <<= aFormat.IsScaleNormalBrackets(); + break; + case HANDLE_PRINTER_NAME: + { + SfxPrinter *pPrinter = pDocSh->GetPrinter ( ); + *pValue <<= pPrinter ? pPrinter->GetName() : OUString(); + } + break; + case HANDLE_PRINTER_SETUP: + { + SfxPrinter *pPrinter = pDocSh->GetPrinter (); + if (pPrinter) + { + SvMemoryStream aStream; + pPrinter->Store( aStream ); + sal_uInt32 nSize = aStream.TellEnd(); + aStream.Seek ( STREAM_SEEK_TO_BEGIN ); + Sequence < sal_Int8 > aSequence ( nSize ); + aStream.ReadBytes(aSequence.getArray(), nSize); + *pValue <<= aSequence; + } + } + break; + case HANDLE_SYMBOLS: + case HANDLE_USED_SYMBOLS: + { + const bool bUsedSymbolsOnly = (*ppEntries)->mnHandle == HANDLE_USED_SYMBOLS; + const std::set< OUString > &rUsedSymbols = pDocSh->GetUsedSymbols(); + + // this is get + SmModule *pp = SM_MOD(); + const SmSymbolManager &rManager = pp->GetSymbolManager(); + std::vector < const SmSym * > aVector; + + const SymbolPtrVec_t aSymbols( rManager.GetSymbols() ); + for (const SmSym* pSymbol : aSymbols) + { + if (pSymbol && !pSymbol->IsPredefined() && + (!bUsedSymbolsOnly || + rUsedSymbols.find( pSymbol->GetUiName() ) != rUsedSymbols.end())) + aVector.push_back ( pSymbol ); + } + Sequence < SymbolDescriptor > aSequence ( aVector.size() ); + SymbolDescriptor * pDescriptor = aSequence.getArray(); + + for (const SmSym* pSymbol : aVector) + { + pDescriptor->sName = pSymbol->GetUiName(); + pDescriptor->sExportName = pSymbol->GetExportName(); + pDescriptor->sSymbolSet = pSymbol->GetSymbolSetName(); + pDescriptor->nCharacter = static_cast < sal_Int32 > (pSymbol->GetCharacter()); + + vcl::Font rFont = pSymbol->GetFace(); + pDescriptor->sFontName = rFont.GetFamilyName(); + pDescriptor->nCharSet = sal::static_int_cast< sal_Int16 >(rFont.GetCharSet()); + pDescriptor->nFamily = sal::static_int_cast< sal_Int16 >(rFont.GetFamilyType()); + pDescriptor->nPitch = sal::static_int_cast< sal_Int16 >(rFont.GetPitch()); + pDescriptor->nWeight = sal::static_int_cast< sal_Int16 >(rFont.GetWeight()); + pDescriptor->nItalic = sal::static_int_cast< sal_Int16 >(rFont.GetItalic()); + pDescriptor++; + } + *pValue <<= aSequence; + } + break; + case HANDLE_BASIC_LIBRARIES: + *pValue <<= pDocSh->GetBasicContainer(); + break; + case HANDLE_DIALOG_LIBRARIES: + *pValue <<= pDocSh->GetDialogContainer(); + break; + case HANDLE_RUNTIME_UID: + *pValue <<= getRuntimeUID(); + break; + // #i33095# Security Options + case HANDLE_LOAD_READONLY : + { + *pValue <<= pDocSh->IsLoadReadonly(); + break; + } + // #i972# + case HANDLE_BASELINE: + { + if ( !pDocSh->GetFormulaTree() ) + pDocSh->Parse(); + if ( pDocSh->GetFormulaTree() ) + { + pDocSh->ArrangeFormula(); + + *pValue <<= static_cast<sal_Int32>( pDocSh->GetFormulaTree()->GetFormulaBaseline() ); + } + break; + } + case HANDLE_INTEROP_GRAB_BAG: + getGrabBagItem(*pValue); + break; + case HANDLE_SAVE_THUMBNAIL: + { + *pValue <<= pDocSh->IsUseThumbnailSave(); + } + break; + case HANDLE_STARMATH_VERSION: + *pValue <<= pDocSh->GetSmSyntaxVersion(); + break; + } + } +} + + +sal_Int32 SAL_CALL SmModel::getRendererCount( + const uno::Any& /*rSelection*/, + const uno::Sequence< beans::PropertyValue >& /*xOptions*/ ) +{ + return 1; +} + +uno::Sequence< beans::PropertyValue > SAL_CALL SmModel::getRenderer( + sal_Int32 nRenderer, + const uno::Any& /*rSelection*/, + const uno::Sequence< beans::PropertyValue >& /*rxOptions*/ ) +{ + SolarMutexGuard aGuard; + + if (0 != nRenderer) + throw IllegalArgumentException(); + + SmDocShell *pDocSh = static_cast < SmDocShell * >( GetObjectShell() ); + if (!pDocSh) + throw RuntimeException(); + + SmPrinterAccess aPrinterAccess( *pDocSh ); + Size aPrtPaperSize; + if (Printer *pPrinter = aPrinterAccess.GetPrinter()) + { + // tdf#157965: UNO methods are expected to return sizes in mm/100 + pPrinter->SetMapMode(MapMode(MapUnit::Map100thMM)); // reset in SmPrinterAccess dtor + aPrtPaperSize = pPrinter->GetPaperSize(); + } + + // if paper size is 0 (usually if no 'real' printer is found), + // guess the paper size + if (aPrtPaperSize.IsEmpty()) + aPrtPaperSize = SvxPaperInfo::GetDefaultPaperSize(MapUnit::Map100thMM); + awt::Size aPageSize( aPrtPaperSize.Width(), aPrtPaperSize.Height() ); + + uno::Sequence< beans::PropertyValue > aRenderer(1); + PropertyValue &rValue = aRenderer.getArray()[0]; + rValue.Name = "PageSize"; + rValue.Value <<= aPageSize; + + if (!m_pPrintUIOptions) + m_pPrintUIOptions.reset(new SmPrintUIOptions); + m_pPrintUIOptions->appendPrintUIOptions( aRenderer ); + + return aRenderer; +} + +void SAL_CALL SmModel::render( + sal_Int32 nRenderer, + const uno::Any& rSelection, + const uno::Sequence< beans::PropertyValue >& rxOptions ) +{ + SolarMutexGuard aGuard; + + if (0 != nRenderer) + throw IllegalArgumentException(); + + SmDocShell *pDocSh = static_cast < SmDocShell * >( GetObjectShell() ); + if (!pDocSh) + throw RuntimeException(); + + // get device to be rendered in + uno::Reference< awt::XDevice > xRenderDevice; + for (const auto& rxOption : rxOptions) + { + if( rxOption.Name == "RenderDevice" ) + rxOption.Value >>= xRenderDevice; + } + + if (!xRenderDevice.is()) + return; + + VCLXDevice* pDevice = dynamic_cast<VCLXDevice*>( xRenderDevice.get() ); + VclPtr< OutputDevice> pOut = pDevice ? pDevice->GetOutputDevice() + : VclPtr< OutputDevice >(); + if (!pOut) + throw RuntimeException(); + + pOut->SetMapMode(MapMode(SmMapUnit())); + + uno::Reference< frame::XModel > xModel; + rSelection >>= xModel; + if (xModel != pDocSh->GetModel()) + return; + + SmPrinterAccess aPrinterAccess( *pDocSh ); + + Size aPrtPaperSize; + Size aOutputSize; + Point aPrtPageOffset; + if (Printer *pPrinter = aPrinterAccess.GetPrinter()) + { + aPrtPaperSize = pPrinter->GetPaperSize(); + aOutputSize = pPrinter->GetOutputSize(); + aPrtPageOffset = pPrinter->GetPageOffset(); + } + + // no real printer ?? + if (aPrtPaperSize.IsEmpty()) + { + aPrtPaperSize = SvxPaperInfo::GetDefaultPaperSize(SmMapUnit()); + // factors from Windows DIN A4 + aOutputSize = Size( static_cast<tools::Long>(aPrtPaperSize.Width() * 0.941), + static_cast<tools::Long>(aPrtPaperSize.Height() * 0.961)); + aPrtPageOffset = Point( static_cast<tools::Long>(aPrtPaperSize.Width() * 0.0250), + static_cast<tools::Long>(aPrtPaperSize.Height() * 0.0214)); + } + tools::Rectangle OutputRect( Point(), aOutputSize ); + + + // set minimum top and bottom border + if (aPrtPageOffset.Y() < 2000) + OutputRect.AdjustTop(2000 - aPrtPageOffset.Y() ); + if ((aPrtPaperSize.Height() - (aPrtPageOffset.Y() + OutputRect.Bottom())) < 2000) + OutputRect.AdjustBottom( -(2000 - (aPrtPaperSize.Height() - + (aPrtPageOffset.Y() + OutputRect.Bottom()))) ); + + // set minimum left and right border + if (aPrtPageOffset.X() < 2500) + OutputRect.AdjustLeft(2500 - aPrtPageOffset.X() ); + if ((aPrtPaperSize.Width() - (aPrtPageOffset.X() + OutputRect.Right())) < 1500) + OutputRect.AdjustRight( -(1500 - (aPrtPaperSize.Width() - + (aPrtPageOffset.X() + OutputRect.Right()))) ); + + if (!m_pPrintUIOptions) + m_pPrintUIOptions.reset(new SmPrintUIOptions); + m_pPrintUIOptions->processProperties( rxOptions ); + + pDocSh->Impl_Print(*pOut, *m_pPrintUIOptions, OutputRect); + + // release SmPrintUIOptions when everything is done. + // That way, when SmPrintUIOptions is needed again it will read the latest configuration settings in its c-tor. + if (m_pPrintUIOptions->getBoolValue( "IsLastPage" )) + { + m_pPrintUIOptions.reset(); + } +} + +void SAL_CALL SmModel::setParent( const uno::Reference< uno::XInterface >& xParent) +{ + SolarMutexGuard aGuard; + SfxBaseModel::setParent( xParent ); + if (SfxObjectShell* pDoc = SfxObjectShell::GetShellFromComponent(xParent)) + GetObjectShell()->OnDocumentPrinterChanged(pDoc->GetDocumentPrinter()); +} + +void SmModel::writeFormulaOoxml( + ::sax_fastparser::FSHelperPtr const pSerializer, + oox::core::OoxmlVersion const version, + oox::drawingml::DocumentType const documentType, sal_Int8 nAlign) +{ + static_cast<SmDocShell*>(GetObjectShell())->writeFormulaOoxml(pSerializer, version, documentType, nAlign); +} + +void SmModel::writeFormulaRtf(OStringBuffer& rBuffer, rtl_TextEncoding nEncoding) +{ + static_cast<SmDocShell*>(GetObjectShell())->writeFormulaRtf(rBuffer, nEncoding); +} + +void SmModel::readFormulaOoxml( oox::formulaimport::XmlStream& stream ) +{ + static_cast< SmDocShell* >( GetObjectShell())->readFormulaOoxml( stream ); +} + +Size SmModel::getFormulaSize() const +{ + return o3tl::convert(static_cast< SmDocShell* >( GetObjectShell())->GetSize(), SmO3tlLengthUnit(), o3tl::Length::mm100); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/utility.cxx b/starmath/source/utility.cxx new file mode 100644 index 0000000000..4a9225f6b3 --- /dev/null +++ b/starmath/source/utility.cxx @@ -0,0 +1,245 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <strings.hrc> +#include <smmod.hxx> +#include <utility.hxx> +#include <dialog.hxx> +#include <view.hxx> + +#include <comphelper/lok.hxx> +#include <sfx2/lokcomponenthelpers.hxx> + +// return pointer to active SmViewShell, if this is not possible +// return 0 instead. +//!! Since this method is based on the current focus it is somewhat +//!! unreliable and may return unexpected 0 pointers! +SmViewShell * SmGetActiveView() +{ + SfxViewShell *pView = SfxViewShell::Current(); + SmViewShell* pSmView = dynamic_cast<SmViewShell*>(pView); + if (!pSmView && comphelper::LibreOfficeKit::isActive()) + { + auto* pWindow = static_cast<SmGraphicWindow*>(LokStarMathHelper(pView).GetGraphicWindow()); + if (pWindow) + pSmView = &pWindow->GetGraphicWidget().GetView(); + } + return pSmView; +} + + +/**************************************************************************/ + +void SmFontPickList::Clear() +{ + aFontVec.clear(); +} + +SmFontPickList& SmFontPickList::operator = (const SmFontPickList& rList) +{ + Clear(); + nMaxItems = rList.nMaxItems; + aFontVec = rList.aFontVec; + + return *this; +} + +vcl::Font SmFontPickList::Get(sal_uInt16 nPos) const +{ + return nPos < aFontVec.size() ? aFontVec[nPos] : vcl::Font(); +} + +namespace { + +bool lcl_CompareItem(const vcl::Font & rFirstFont, const vcl::Font & rSecondFont) +{ + return rFirstFont.GetFamilyName() == rSecondFont.GetFamilyName() && + rFirstFont.GetFamilyType() == rSecondFont.GetFamilyType() && + rFirstFont.GetCharSet() == rSecondFont.GetCharSet() && + rFirstFont.GetWeight() == rSecondFont.GetWeight() && + rFirstFont.GetItalic() == rSecondFont.GetItalic(); +} + +OUString lcl_GetStringItem(const vcl::Font &rFont) +{ + OUStringBuffer aString(rFont.GetFamilyName()); + + if (IsItalic( rFont )) + { + aString.append(", " + SmResId(RID_FONTITALIC)); + } + if (IsBold( rFont )) + { + aString.append(", " + SmResId(RID_FONTBOLD)); + } + + return aString.makeStringAndClear(); +} + +} + +void SmFontPickList::Insert(const vcl::Font &rFont) +{ + for (size_t nPos = 0; nPos < aFontVec.size(); nPos++) + if (lcl_CompareItem( aFontVec[nPos], rFont)) + { + aFontVec.erase( aFontVec.begin() + nPos ); + break; + } + + aFontVec.push_front( rFont ); + + if (aFontVec.size() > nMaxItems) + { + aFontVec.pop_back(); + } +} + +void SmFontPickList::ReadFrom(const SmFontDialog& rDialog) +{ + Insert(rDialog.GetFont()); +} + +void SmFontPickList::WriteTo(SmFontDialog& rDialog) const +{ + rDialog.SetFont(Get()); +} + + +/**************************************************************************/ + +SmFontPickListBox::SmFontPickListBox(std::unique_ptr<weld::ComboBox> pWidget) + : SmFontPickList(4) + , m_xWidget(std::move(pWidget)) +{ + m_xWidget->connect_changed(LINK(this, SmFontPickListBox, SelectHdl)); +} + +IMPL_LINK_NOARG(SmFontPickListBox, SelectHdl, weld::ComboBox&, void) +{ + const int nPos = m_xWidget->get_active(); + if (nPos != 0) + { + SmFontPickList::Insert(Get(nPos)); + OUString aString = m_xWidget->get_text(nPos); + m_xWidget->remove(nPos); + m_xWidget->insert_text(0, aString); + } + + m_xWidget->set_active(0); +} + +SmFontPickListBox& SmFontPickListBox::operator=(const SmFontPickList& rList) +{ + *static_cast<SmFontPickList *>(this) = rList; + + for (decltype(aFontVec)::size_type nPos = 0; nPos < aFontVec.size(); nPos++) + m_xWidget->insert_text(nPos, lcl_GetStringItem(aFontVec[nPos])); + + if (!aFontVec.empty()) + m_xWidget->set_active_text(lcl_GetStringItem(aFontVec.front())); + + return *this; +} + +void SmFontPickListBox::Insert(const vcl::Font &rFont) +{ + SmFontPickList::Insert(rFont); + + OUString aEntry(lcl_GetStringItem(aFontVec.front())); + int nPos = m_xWidget->find_text(aEntry); + if (nPos != -1) + m_xWidget->remove(nPos); + m_xWidget->insert_text(0, aEntry); + m_xWidget->set_active(0); + + while (m_xWidget->get_count() > nMaxItems) + m_xWidget->remove(m_xWidget->get_count() - 1); +} + +bool IsItalic( const vcl::Font &rFont ) +{ + FontItalic eItalic = rFont.GetItalic(); + // the code below leaves only _NONE and _DONTKNOW as not italic + return eItalic == ITALIC_OBLIQUE || eItalic == ITALIC_NORMAL; +} + + +bool IsBold( const vcl::Font &rFont ) +{ + FontWeight eWeight = rFont.GetWeight(); + return eWeight > WEIGHT_NORMAL; +} + + +void SmFace::Impl_Init() +{ + SetSize( GetFontSize() ); + SetTransparent( true ); + SetAlignment( ALIGN_BASELINE ); + SetColor( COL_AUTO ); +} + +void SmFace::SetSize(const Size& rSize) +{ + Size aSize (rSize); + + // check the requested size against minimum value + const int nMinVal = o3tl::convert(2, o3tl::Length::pt, SmO3tlLengthUnit()); + + if (aSize.Height() < nMinVal) + aSize.setHeight( nMinVal ); + + //! we don't force a maximum value here because this may prevent eg the + //! parentheses in "left ( ... right )" from matching up with large + //! bodies (eg stack{...} with many entries). + //! Of course this is holds only if characters are used and not polygons. + + Font::SetFontSize(aSize); +} + + +tools::Long SmFace::GetBorderWidth() const +{ + if (nBorderWidth < 0) + return GetDefaultBorderWidth(); + else + return nBorderWidth; +} + +SmFace & SmFace::operator = (const SmFace &rFace) +{ + Font::operator = (rFace); + nBorderWidth = -1; + return *this; +} + + +SmFace & operator *= (SmFace &rFace, const Fraction &rFrac) + // scales the width and height of 'rFace' by 'rFrac' and returns a + // reference to 'rFace'. + // It's main use is to make scaling fonts look easier. +{ const Size &rFaceSize = rFace.GetFontSize(); + + rFace.SetSize(Size(tools::Long(rFaceSize.Width() * rFrac), + tools::Long(rFaceSize.Height() * rFrac))); + return rFace; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/view.cxx b/starmath/source/view.cxx new file mode 100644 index 0000000000..8f88b5ced6 --- /dev/null +++ b/starmath/source/view.cxx @@ -0,0 +1,2239 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <com/sun/star/accessibility/AccessibleEventId.hpp> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/frame/XFramesSupplier.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/container/XChild.hpp> + +#include <comphelper/lok.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/servicehelper.hxx> +#include <comphelper/storagehelper.hxx> +#include <comphelper/string.hxx> +#include <i18nutil/unicode.hxx> +#include <LibreOfficeKit/LibreOfficeKitEnums.h> +#include <officecfg/Office/Common.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/docfile.hxx> +#include <sfx2/docfilt.hxx> +#include <sfx2/docinsert.hxx> +#include <sfx2/filedlghelper.hxx> +#include <sfx2/infobar.hxx> +#include <sfx2/lokcomponenthelpers.hxx> +#include <sfx2/lokhelper.hxx> +#include <sfx2/msg.hxx> +#include <sfx2/objface.hxx> +#include <sfx2/printer.hxx> +#include <sfx2/request.hxx> +#include <sfx2/sfxbasecontroller.hxx> +#include <sfx2/sidebar/Sidebar.hxx> +#include <sfx2/sidebar/SidebarChildWindow.hxx> +#include <sfx2/sidebar/SidebarController.hxx> +#include <sfx2/viewfac.hxx> +#include <svl/eitem.hxx> +#include <svl/itemset.hxx> +#include <svl/poolitem.hxx> +#include <svl/stritem.hxx> +#include <svl/voiditem.hxx> +#include <vcl/transfer.hxx> +#include <svtools/colorcfg.hxx> +#include <svl/whiter.hxx> +#include <svx/sidebar/SelectionChangeHandler.hxx> +#include <svx/zoomslideritem.hxx> +#include <editeng/editeng.hxx> +#include <editeng/editview.hxx> +#include <editeng/editund2.hxx> +#include <svx/svxdlg.hxx> +#include <sfx2/zoomitem.hxx> +#include <vcl/commandevent.hxx> +#include <vcl/event.hxx> +#include <vcl/help.hxx> +#include <vcl/settings.hxx> +#include <vcl/virdev.hxx> +#include <sal/log.hxx> +#include <tools/svborder.hxx> +#include <o3tl/string_view.hxx> +#include <o3tl/temporary.hxx> + +#include <unotools/streamwrap.hxx> + +#include <unomodel.hxx> +#include <view.hxx> +#include <cfgitem.hxx> +#include <dialog.hxx> +#include <document.hxx> +#include <starmath.hrc> +#include <strings.hrc> +#include <smmod.hxx> +#include <mathmlimport.hxx> +#include <cursor.hxx> +#include "accessibility.hxx" +#include <ElementsDockingWindow.hxx> +#include <helpids.h> + +// space around the edit window, in pixels +// fdo#69111: Increased border on the top so that the window is +// easier to tear off. +#define CMD_BOX_PADDING 3 +#define CMD_BOX_PADDING_TOP 11 + +#define ShellClass_SmViewShell +#include <smslots.hxx> + +using namespace css; +using namespace css::accessibility; +using namespace css::uno; + +SmGraphicWindow::SmGraphicWindow(SmViewShell& rShell) + : InterimItemWindow(&rShell.GetViewFrame().GetWindow(), "modules/smath/ui/mathwindow.ui", "MathWindow") + , nLinePixH(GetSettings().GetStyleSettings().GetScrollBarSize()) + , nColumnPixW(nLinePixH) + , nZoom(100) + // continue to use user-scrolling to make this work equivalent to how it 'always' worked + , mxScrolledWindow(m_xBuilder->weld_scrolled_window("scrolledwindow", true)) + , mxGraphic(new SmGraphicWidget(rShell, *this)) + , mxGraphicWin(new weld::CustomWeld(*m_xBuilder, "mathview", *mxGraphic)) +{ + InitControlBase(mxGraphic->GetDrawingArea()); + + mxScrolledWindow->connect_hadjustment_changed(LINK(this, SmGraphicWindow, ScrollHdl)); + mxScrolledWindow->connect_vadjustment_changed(LINK(this, SmGraphicWindow, ScrollHdl)); + + // docking windows are usually hidden (often already done in the + // resource) and will be shown by the sfx framework. + Hide(); +} + +void SmGraphicWindow::dispose() +{ + InitControlBase(nullptr); + mxGraphicWin.reset(); + mxGraphic.reset(); + mxScrolledWindow.reset(); + InterimItemWindow::dispose(); +} + +SmGraphicWindow::~SmGraphicWindow() +{ + disposeOnce(); +} + +void SmGraphicWindow::Resize() +{ + InterimItemWindow::Resize(); + + // get the new output-size in pixel + Size aOutPixSz = GetOutputSizePixel(); + + // determine the size of the output-area and if we need scrollbars + const auto nScrSize = mxScrolledWindow->get_scroll_thickness(); + bool bVVisible = false; // by default no vertical-ScrollBar + bool bHVisible = false; // by default no horizontal-ScrollBar + bool bChanged; // determines if a visibility was changed + do + { + bChanged = false; + + // does we need a vertical ScrollBar + if ( aOutPixSz.Width() < aTotPixSz.Width() && !bHVisible ) + { + bHVisible = true; + aOutPixSz.AdjustHeight( -nScrSize ); + bChanged = true; + } + + // does we need a horizontal ScrollBar + if ( aOutPixSz.Height() < aTotPixSz.Height() && !bVVisible ) + { + bVVisible = true; + aOutPixSz.AdjustWidth( -nScrSize ); + bChanged = true; + } + + } + while ( bChanged ); // until no visibility has changed + + // store the old offset and map-mode + MapMode aMap(GetGraphicMapMode()); + Point aOldPixOffset(aPixOffset); + + // justify (right/bottom borders should never exceed the virtual window) + Size aPixDelta; + if ( aPixOffset.X() < 0 && + aPixOffset.X() + aTotPixSz.Width() < aOutPixSz.Width() ) + aPixDelta.setWidth( + aOutPixSz.Width() - ( aPixOffset.X() + aTotPixSz.Width() ) ); + if ( aPixOffset.Y() < 0 && + aPixOffset.Y() + aTotPixSz.Height() < aOutPixSz.Height() ) + aPixDelta.setHeight( + aOutPixSz.Height() - ( aPixOffset.Y() + aTotPixSz.Height() ) ); + if ( aPixDelta.Width() || aPixDelta.Height() ) + { + aPixOffset.AdjustX(aPixDelta.Width() ); + aPixOffset.AdjustY(aPixDelta.Height() ); + } + + // for axis without scrollbar restore the origin + if ( !bVVisible || !bHVisible ) + { + aPixOffset = Point( + bHVisible + ? aPixOffset.X() + : (aOutPixSz.Width()-aTotPixSz.Width()) / 2, + bVVisible + ? aPixOffset.Y() + : (aOutPixSz.Height()-aTotPixSz.Height()) / 2 ); + } + if (bHVisible && mxScrolledWindow->get_hpolicy() == VclPolicyType::NEVER) + aPixOffset.setX( 0 ); + if (bVVisible && mxScrolledWindow->get_vpolicy() == VclPolicyType::NEVER) + aPixOffset.setY( 0 ); + + // select the shifted map-mode + if (aPixOffset != aOldPixOffset) + SetGraphicMapMode(aMap); + + // show or hide scrollbars + mxScrolledWindow->set_vpolicy(bVVisible ? VclPolicyType::ALWAYS : VclPolicyType::NEVER); + mxScrolledWindow->set_hpolicy(bHVisible ? VclPolicyType::ALWAYS : VclPolicyType::NEVER); + + // resize scrollbars and set their ranges + if ( bHVisible ) + { + mxScrolledWindow->hadjustment_configure(-aPixOffset.X(), 0, aTotPixSz.Width(), nColumnPixW, + aOutPixSz.Width(), aOutPixSz.Width()); + } + if ( bVVisible ) + { + mxScrolledWindow->vadjustment_configure(-aPixOffset.Y(), 0, aTotPixSz.Height(), nLinePixH, + aOutPixSz.Height(), aOutPixSz.Height()); + } +} + +IMPL_LINK_NOARG(SmGraphicWindow, ScrollHdl, weld::ScrolledWindow&, void) +{ + MapMode aMap(GetGraphicMapMode()); + Point aNewPixOffset(aPixOffset); + + // scrolling horizontally? + if (mxScrolledWindow->get_hpolicy() == VclPolicyType::ALWAYS) + aNewPixOffset.setX(-mxScrolledWindow->hadjustment_get_value()); + + // scrolling vertically? + if (mxScrolledWindow->get_vpolicy() == VclPolicyType::ALWAYS) + aNewPixOffset.setY(-mxScrolledWindow->vadjustment_get_value()); + + // scrolling? + if (aPixOffset == aNewPixOffset) + return; + + // recompute the logical scroll units + aPixOffset = aNewPixOffset; + + SetGraphicMapMode(aMap); +} + +void SmGraphicWindow::SetGraphicMapMode(const MapMode& rNewMapMode) +{ + OutputDevice& rDevice = mxGraphic->GetOutputDevice(); + MapMode aMap( rNewMapMode ); + aMap.SetOrigin( aMap.GetOrigin() + rDevice.PixelToLogic( aPixOffset, aMap ) ); + rDevice.SetMapMode( aMap ); + mxGraphic->Invalidate(); +} + +MapMode SmGraphicWindow::GetGraphicMapMode() const +{ + OutputDevice& rDevice = mxGraphic->GetOutputDevice(); + MapMode aMap(rDevice.GetMapMode()); + aMap.SetOrigin( aMap.GetOrigin() - rDevice.PixelToLogic( aPixOffset ) ); + return aMap; +} + +void SmGraphicWindow::SetTotalSize( const Size& rNewSize ) +{ + aTotPixSz = mxGraphic->GetOutputDevice().LogicToPixel(rNewSize); + Resize(); +} + +Size SmGraphicWindow::GetTotalSize() const +{ + return mxGraphic->GetOutputDevice().PixelToLogic(aTotPixSz); +} + +void SmGraphicWindow::ShowContextMenu(const CommandEvent& rCEvt) +{ + GetParent()->ToTop(); + Point aPos(5, 5); + if (rCEvt.IsMouseEvent()) + aPos = rCEvt.GetMousePosPixel(); + + // added for replaceability of context menus + SfxDispatcher::ExecutePopup( this, &aPos ); +} + +SmGraphicWidget::SmGraphicWidget(SmViewShell& rShell, SmGraphicWindow& rGraphicWindow) + : mrGraphicWindow(rGraphicWindow) + , bIsCursorVisible(false) + , bIsLineVisible(false) + , aCaretBlinkTimer("SmGraphicWidget aCaretBlinkTimer") + , mrViewShell(rShell) +{ +} + +void SmGraphicWidget::SetDrawingArea(weld::DrawingArea* pDrawingArea) +{ + weld::CustomWidgetController::SetDrawingArea(pDrawingArea); + + OutputDevice& rDevice = GetOutputDevice(); + + rDevice.EnableRTL(GetDoc()->GetFormat().IsRightToLeft()); + rDevice.SetBackground(SM_MOD()->GetColorConfig().GetColorValue(svtools::DOCCOLOR).nColor); + + if (comphelper::LibreOfficeKit::isActive()) + { + // Disable map mode, so that it's possible to send mouse event coordinates + // directly in twips. + rDevice.EnableMapMode(false); + } + else + { + const Fraction aFraction(1, 1); + rDevice.SetMapMode(MapMode(SmMapUnit(), Point(), aFraction, aFraction)); + } + + SetTotalSize(); + + SetHelpId(HID_SMA_WIN_DOCUMENT); + + ShowLine(false); + CaretBlinkInit(); +} + +SmGraphicWidget::~SmGraphicWidget() +{ + if (mxAccessible.is()) + mxAccessible->ClearWin(); // make Accessible nonfunctional + mxAccessible.clear(); + CaretBlinkStop(); +} + +SmDocShell* SmGraphicWidget::GetDoc() { return GetView().GetDoc(); } + +SmCursor& SmGraphicWidget::GetCursor() +{ + assert(GetDoc()); + return GetDoc()->GetCursor(); +} + +bool SmGraphicWidget::MouseButtonDown(const MouseEvent& rMEvt) +{ + GrabFocus(); + + // set formula-cursor and selection of edit window according to the + // position clicked at + + SAL_WARN_IF( rMEvt.GetClicks() == 0, "starmath", "0 clicks" ); + if ( !rMEvt.IsLeft() ) + return true; + + OutputDevice& rDevice = GetOutputDevice(); + // get click position relative to formula + Point aPos(rDevice.PixelToLogic(rMEvt.GetPosPixel()) - GetFormulaDrawPos()); + + const SmNode *pTree = GetDoc()->GetFormulaTree(); + if (!pTree) + return true; + + SmEditWindow* pEdit = GetView().GetEditWindow(); + + if (SmViewShell::IsInlineEditEnabled()) { + GetCursor().MoveTo(&rDevice, aPos, !rMEvt.IsShift()); + GetView().InvalidateSlots(); + // 'on grab' window events are missing in lok, do it explicitly + if (comphelper::LibreOfficeKit::isActive()) + SetIsCursorVisible(true); + return true; + } + const SmNode *pNode = nullptr; + // if it was clicked inside the formula then get the appropriate node + if (pTree->OrientedDist(aPos) <= 0) + pNode = pTree->FindRectClosestTo(aPos); + + if (!pNode) + return true; + + if (!pEdit) + return true; + + // set selection to the beginning of the token + pEdit->SetSelection(pNode->GetSelection()); + SetCursor(pNode); + + // allow for immediate editing and + //! implicitly synchronize the cursor position mark in this window + pEdit->GrabFocus(); + + return true; +} + +bool SmGraphicWidget::MouseMove(const MouseEvent &rMEvt) +{ + if (rMEvt.IsLeft() && SmViewShell::IsInlineEditEnabled()) + { + OutputDevice& rDevice = GetOutputDevice(); + Point aPos(rDevice.PixelToLogic(rMEvt.GetPosPixel()) - GetFormulaDrawPos()); + GetCursor().MoveTo(&rDevice, aPos, false); + + CaretBlinkStop(); + SetIsCursorVisible(true); + CaretBlinkStart(); + RepaintViewShellDoc(); + } + return true; +} + +void SmGraphicWidget::GetFocus() +{ + if (!SmViewShell::IsInlineEditEnabled()) + return; + if (SmEditWindow* pEdit = GetView().GetEditWindow()) + pEdit->Flush(); + SetIsCursorVisible(true); + ShowLine(true); + CaretBlinkStart(); + RepaintViewShellDoc(); +} + +void SmGraphicWidget::LoseFocus() +{ + if (mxAccessible.is()) + { + uno::Any aOldValue, aNewValue; + aOldValue <<= AccessibleStateType::FOCUSED; + // aNewValue remains empty + mxAccessible->LaunchEvent( AccessibleEventId::STATE_CHANGED, + aOldValue, aNewValue ); + } + if (!SmViewShell::IsInlineEditEnabled()) + return; + SetIsCursorVisible(false); + ShowLine(false); + CaretBlinkStop(); + RepaintViewShellDoc(); +} + +void SmGraphicWidget::RepaintViewShellDoc() +{ + if (SmDocShell* pDoc = GetDoc()) + pDoc->Repaint(); +} + +IMPL_LINK_NOARG(SmGraphicWidget, CaretBlinkTimerHdl, Timer *, void) +{ + if (IsCursorVisible()) + SetIsCursorVisible(false); + else + SetIsCursorVisible(true); + + RepaintViewShellDoc(); +} + +void SmGraphicWidget::CaretBlinkInit() +{ + if (comphelper::LibreOfficeKit::isActive()) + return; // No blinking in lok case + aCaretBlinkTimer.SetInvokeHandler(LINK(this, SmGraphicWidget, CaretBlinkTimerHdl)); + aCaretBlinkTimer.SetTimeout(Application::GetSettings().GetStyleSettings().GetCursorBlinkTime()); +} + +void SmGraphicWidget::CaretBlinkStart() +{ + if (!SmViewShell::IsInlineEditEnabled() || comphelper::LibreOfficeKit::isActive()) + return; + if (aCaretBlinkTimer.GetTimeout() != STYLE_CURSOR_NOBLINKTIME) + aCaretBlinkTimer.Start(); +} + +void SmGraphicWidget::CaretBlinkStop() +{ + if (!SmViewShell::IsInlineEditEnabled() || comphelper::LibreOfficeKit::isActive()) + return; + aCaretBlinkTimer.Stop(); +} + +// shows or hides the formula-cursor depending on 'bShow' is true or not +void SmGraphicWidget::ShowCursor(bool bShow) +{ + if (SmViewShell::IsInlineEditEnabled()) + return; + + bool bInvert = bShow != IsCursorVisible(); + if (bInvert) + InvertFocusRect(GetOutputDevice(), aCursorRect); + + SetIsCursorVisible(bShow); +} + +void SmGraphicWidget::ShowLine(bool bShow) +{ + if (!SmViewShell::IsInlineEditEnabled()) + return; + + bIsLineVisible = bShow; +} + +void SmGraphicWidget::SetIsCursorVisible(bool bVis) +{ + bIsCursorVisible = bVis; + if (comphelper::LibreOfficeKit::isActive()) + { + mrViewShell.SendCaretToLOK(); + mrViewShell.libreOfficeKitViewCallback(LOK_CALLBACK_CURSOR_VISIBLE, + OString::boolean(bVis)); + } +} + +void SmGraphicWidget::SetCursor(const SmNode *pNode) +{ + if (SmViewShell::IsInlineEditEnabled()) + return; + + const SmNode *pTree = GetDoc()->GetFormulaTree(); + + // get appropriate rectangle + Point aOffset (pNode->GetTopLeft() - pTree->GetTopLeft()), + aTLPos (GetFormulaDrawPos() + aOffset); + aTLPos.AdjustX( -(pNode->GetItalicLeftSpace()) ); + Size aSize (pNode->GetItalicSize()); + + SetCursor(tools::Rectangle(aTLPos, aSize)); +} + +void SmGraphicWidget::SetCursor(const tools::Rectangle &rRect) + // sets cursor to new position (rectangle) 'rRect'. + // The old cursor will be removed, and the new one will be shown if + // that is activated in the ConfigItem +{ + if (SmViewShell::IsInlineEditEnabled()) + return; + + SmModule *pp = SM_MOD(); + + if (IsCursorVisible()) + ShowCursor(false); // clean up remainings of old cursor + aCursorRect = rRect; + if (pp->GetConfig()->IsShowFormulaCursor()) + ShowCursor(true); // draw new cursor +} + +const SmNode * SmGraphicWidget::SetCursorPos(sal_uInt16 nRow, sal_uInt16 nCol) + // looks for a VISIBLE node in the formula tree with its token at + // (or around) the position 'nRow', 'nCol' in the edit window + // (row and column numbering starts with 1 there!). + // If there is such a node the formula-cursor is set to cover that nodes + // rectangle. If not the formula-cursor will be hidden. + // In any case the search result is being returned. +{ + if (SmViewShell::IsInlineEditEnabled()) + return nullptr; + + // find visible node with token at nRow, nCol + const SmNode *pTree = GetDoc()->GetFormulaTree(), + *pNode = nullptr; + if (pTree) + pNode = pTree->FindTokenAt(nRow, nCol); + + if (pNode) + SetCursor(pNode); + else + ShowCursor(false); + + return pNode; +} + +void SmGraphicWidget::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&) +{ + assert(GetDoc()); + SmDocShell& rDoc = *GetDoc(); + Point aPoint; + + rDoc.DrawFormula(rRenderContext, aPoint, true); //! modifies aPoint to be the topleft + //! corner of the formula + aFormulaDrawPos = aPoint; + if (SmViewShell::IsInlineEditEnabled()) + { + //Draw cursor if any... + if (rDoc.HasCursor() && IsLineVisible()) + rDoc.GetCursor().Draw(rRenderContext, aPoint, IsCursorVisible()); + } + else + { + SetIsCursorVisible(false); // (old) cursor must be drawn again + + if (const SmEditWindow* pEdit = GetView().GetEditWindow()) + { // get new position for formula-cursor (for possible altered formula) + sal_Int32 nRow; + sal_uInt16 nCol; + SmGetLeftSelectionPart(pEdit->GetSelection(), nRow, nCol); + const SmNode *pFound = SetCursorPos(static_cast<sal_uInt16>(nRow), nCol); + + SmModule *pp = SM_MOD(); + if (pFound && pp->GetConfig()->IsShowFormulaCursor()) + ShowCursor(true); + } + } +} + +void SmGraphicWidget::SetTotalSize() +{ + assert(GetDoc()); + OutputDevice& rDevice = GetOutputDevice(); + const Size aTmp(rDevice.PixelToLogic(rDevice.LogicToPixel(GetDoc()->GetSize()))); + if (aTmp != mrGraphicWindow.GetTotalSize()) + mrGraphicWindow.SetTotalSize(aTmp); +} + +namespace +{ +SmBracketType BracketTypeOf(sal_uInt32 c) +{ + switch (c) + { + case '(': + case ')': + return SmBracketType::Round; + case '[': + case ']': + return SmBracketType::Square; + case '{': + case '}': + return SmBracketType::Curly; + } + assert(false); // Unreachable + return SmBracketType::Round; +} + +bool CharInput(sal_uInt32 c, SmCursor& rCursor, OutputDevice& rDevice) +{ + switch (c) + { + case 0: + return false; + case ' ': + rCursor.InsertElement(BlankElement); + break; + case '!': + rCursor.InsertElement(FactorialElement); + break; + case '%': + rCursor.InsertElement(PercentElement); + break; + case '*': + rCursor.InsertElement(CDotElement); + break; + case '+': + rCursor.InsertElement(PlusElement); + break; + case '-': + rCursor.InsertElement(MinusElement); + break; + case '<': + rCursor.InsertElement(LessThanElement); + break; + case '=': + rCursor.InsertElement(EqualElement); + break; + case '>': + rCursor.InsertElement(GreaterThanElement); + break; + case '^': + rCursor.InsertSubSup(RSUP); + break; + case '_': + rCursor.InsertSubSup(RSUB); + break; + case '/': + rCursor.InsertFraction(); + break; + case '(': + case '[': + case '{': + rCursor.InsertBrackets(BracketTypeOf(c)); + break; + case ')': + case ']': + case '}': + if (rCursor.IsAtTailOfBracket(BracketTypeOf(c))) + { + rCursor.Move(&rDevice, MoveRight); + break; + } + [[fallthrough]]; + default: + rCursor.InsertText(OUString(&c, 1)); + break; + } + return true; +} +} + +bool SmGraphicWidget::KeyInput(const KeyEvent& rKEvt) +{ + if (rKEvt.GetKeyCode().GetCode() == KEY_F1) + { + GetView().StartMainHelp(); + return true; + } + + if (rKEvt.GetKeyCode().GetCode() == KEY_ESCAPE) + { + // Terminate possible InPlace mode + return GetView().Escape(); + } + + if (!SmViewShell::IsInlineEditEnabled()) + return GetView().KeyInput(rKEvt); + + bool bConsumed = true; + + SmCursor& rCursor = GetCursor(); + switch (rKEvt.GetKeyCode().GetFunction()) + { + case KeyFuncType::COPY: + rCursor.Copy(&mrGraphicWindow); + break; + case KeyFuncType::CUT: + rCursor.Cut(&mrGraphicWindow); + break; + case KeyFuncType::PASTE: + rCursor.Paste(&mrGraphicWindow); + break; + case KeyFuncType::UNDO: + GetDoc()->Execute(o3tl::temporary(SfxRequest(*GetView().GetFrame(), SID_UNDO))); + break; + case KeyFuncType::REDO: + GetDoc()->Execute(o3tl::temporary(SfxRequest(*GetView().GetFrame(), SID_REDO))); + break; + default: + switch (rKEvt.GetKeyCode().GetCode()) + { + case KEY_LEFT: + rCursor.Move(&GetOutputDevice(), MoveLeft, !rKEvt.GetKeyCode().IsShift()); + break; + case KEY_RIGHT: + rCursor.Move(&GetOutputDevice(), MoveRight, !rKEvt.GetKeyCode().IsShift()); + break; + case KEY_UP: + rCursor.Move(&GetOutputDevice(), MoveUp, !rKEvt.GetKeyCode().IsShift()); + break; + case KEY_DOWN: + rCursor.Move(&GetOutputDevice(), MoveDown, !rKEvt.GetKeyCode().IsShift()); + break; + case KEY_RETURN: + if (!rKEvt.GetKeyCode().IsShift()) + rCursor.InsertRow(); + break; + case KEY_DELETE: + if (!rCursor.HasSelection()) + { + rCursor.Move(&GetOutputDevice(), MoveRight, false); + if (rCursor.HasComplexSelection()) + break; + } + rCursor.Delete(); + break; + case KEY_BACKSPACE: + rCursor.DeletePrev(&GetOutputDevice()); + break; + default: + if (!CharInput(rKEvt.GetCharCode(), rCursor, GetOutputDevice())) + bConsumed = GetView().KeyInput(rKEvt); + } + } + + GetView().InvalidateSlots(); + CaretBlinkStop(); + CaretBlinkStart(); + SetIsCursorVisible(true); + RepaintViewShellDoc(); + + return bConsumed; +} + +bool SmGraphicWidget::Command(const CommandEvent& rCEvt) +{ + bool bCallBase = true; + if (!GetView().GetViewFrame().GetFrame().IsInPlace()) + { + switch ( rCEvt.GetCommand() ) + { + case CommandEventId::ContextMenu: + // purely for "ExecutePopup" taking a vcl::Window and + // we assume SmGraphicWindow 0,0 is at SmEditWindow 0,0 + mrGraphicWindow.ShowContextMenu(rCEvt); + bCallBase = false; + break; + + case CommandEventId::Wheel: + { + const CommandWheelData* pWData = rCEvt.GetWheelData(); + if ( pWData && CommandWheelMode::ZOOM == pWData->GetMode() ) + { + sal_uInt16 nTmpZoom = mrGraphicWindow.GetZoom(); + if( 0 > pWData->GetDelta() ) + nTmpZoom -= 10; + else + nTmpZoom += 10; + mrGraphicWindow.SetZoom(nTmpZoom); + bCallBase = false; + } + break; + } + case CommandEventId::GestureZoom: + { + const CommandGestureZoomData* pData = rCEvt.GetGestureZoomData(); + if (pData) + { + if (pData->meEventType == GestureEventZoomType::Begin) + { + mfLastZoomScale = pData->mfScaleDelta; + } + else if (pData->meEventType == GestureEventZoomType::Update) + { + double deltaBetweenEvents = (pData->mfScaleDelta - mfLastZoomScale) / mfLastZoomScale; + mfLastZoomScale = pData->mfScaleDelta; + + // Accumulate fractional zoom to avoid small zoom changes from being ignored + mfAccumulatedZoom += deltaBetweenEvents; + int nZoomChangePercent = mfAccumulatedZoom * 100; + mfAccumulatedZoom -= nZoomChangePercent / 100.0; + + sal_uInt16 nZoom = mrGraphicWindow.GetZoom(); + nZoom += nZoomChangePercent; + mrGraphicWindow.SetZoom(nZoom); + } + bCallBase = false; + } + break; + } + + default: break; + } + } + + switch (rCEvt.GetCommand()) + { + case CommandEventId::ExtTextInput: + if (SmViewShell::IsInlineEditEnabled()) + { + const CommandExtTextInputData* pData = rCEvt.GetExtTextInputData(); + assert(pData); + const OUString& rText = pData->GetText(); + SmCursor& rCursor = GetCursor(); + OutputDevice& rDevice = GetOutputDevice(); + for (sal_Int32 i = 0; i < rText.getLength();) + CharInput(rText.iterateCodePoints(&i), rCursor, rDevice); + bCallBase = false; + } + break; + default: + break; + } + return !bCallBase; +} + +void SmGraphicWindow::SetZoom(sal_uInt16 Factor) +{ + if (comphelper::LibreOfficeKit::isActive()) + return; + nZoom = std::clamp(Factor, MINZOOM, MAXZOOM); + Fraction aFraction(nZoom, 100); + SetGraphicMapMode(MapMode(SmMapUnit(), Point(), aFraction, aFraction)); + mxGraphic->SetTotalSize(); + SmViewShell& rViewSh = mxGraphic->GetView(); + rViewSh.GetViewFrame().GetBindings().Invalidate(SID_ATTR_ZOOM); + rViewSh.GetViewFrame().GetBindings().Invalidate(SID_ATTR_ZOOMSLIDER); +} + +void SmGraphicWindow::ZoomToFitInWindow() +{ + // set defined mapmode before calling 'LogicToPixel' below + SetGraphicMapMode(MapMode(SmMapUnit())); + + assert(mxGraphic->GetDoc()); + Size aSize(mxGraphic->GetOutputDevice().LogicToPixel(mxGraphic->GetDoc()->GetSize())); + Size aWindowSize(GetSizePixel()); + + if (!aSize.IsEmpty()) + { + tools::Long nVal = std::min ((85 * aWindowSize.Width()) / aSize.Width(), + (85 * aWindowSize.Height()) / aSize.Height()); + SetZoom ( sal::static_int_cast< sal_uInt16 >(nVal) ); + } +} + +uno::Reference< XAccessible > SmGraphicWidget::CreateAccessible() +{ + if (!mxAccessible.is()) + { + mxAccessible = new SmGraphicAccessible( this ); + } + return mxAccessible; +} + +/**************************************************************************/ +SmGraphicController::SmGraphicController(SmGraphicWidget &rSmGraphic, + sal_uInt16 nId_, + SfxBindings &rBindings) : + SfxControllerItem(nId_, rBindings), + rGraphic(rSmGraphic) +{ +} + +void SmGraphicController::StateChangedAtToolBoxControl(sal_uInt16 nSID, SfxItemState eState, const SfxPoolItem* pState) +{ + rGraphic.SetTotalSize(); + rGraphic.Invalidate(); + SfxControllerItem::StateChangedAtToolBoxControl (nSID, eState, pState); +} + +/**************************************************************************/ +SmEditController::SmEditController(SmEditWindow &rSmEdit, + sal_uInt16 nId_, + SfxBindings &rBindings) : + SfxControllerItem(nId_, rBindings), + rEdit(rSmEdit) +{ +} + +void SmEditController::StateChangedAtToolBoxControl(sal_uInt16 nSID, SfxItemState eState, const SfxPoolItem* pState) +{ + const SfxStringItem *pItem = dynamic_cast<const SfxStringItem*>( pState); + + if ((pItem != nullptr) && (rEdit.GetText() != pItem->GetValue())) + rEdit.SetText(pItem->GetValue()); + SfxControllerItem::StateChangedAtToolBoxControl (nSID, eState, pState); +} + +/**************************************************************************/ +SmCmdBoxWindow::SmCmdBoxWindow(SfxBindings *pBindings_, SfxChildWindow *pChildWindow, + vcl::Window *pParent) + : SfxDockingWindow(pBindings_, pChildWindow, pParent, "EditWindow", "modules/smath/ui/editwindow.ui") + , m_xEdit(new SmEditWindow(*this, *m_xBuilder)) + , aController(*m_xEdit, SID_TEXT, *pBindings_) + , bExiting(false) + , aInitialFocusTimer("SmCmdBoxWindow aInitialFocusTimer") +{ + set_id("math_edit"); + + SetHelpId( HID_SMA_COMMAND_WIN ); + SetSizePixel(LogicToPixel(Size(292 , 94), MapMode(MapUnit::MapAppFont))); + SetText(SmResId(STR_CMDBOXWINDOW)); + + Hide(); + + // Don't try to grab focus in inline edit mode + if (!SmViewShell::IsInlineEditEnabled()) + { + aInitialFocusTimer.SetInvokeHandler(LINK(this, SmCmdBoxWindow, InitialFocusTimerHdl)); + aInitialFocusTimer.SetTimeout(100); + } +} + +Point SmCmdBoxWindow::WidgetToWindowPos(const weld::Widget& rWidget, const Point& rPos) +{ + Point aRet(rPos); + int x(0), y(0), width(0), height(0); + rWidget.get_extents_relative_to(*m_xContainer, x, y, width, height); + aRet.Move(x, y); + aRet.Move(m_xBox->GetPosPixel().X(), m_xBox->GetPosPixel().Y()); + return aRet; +} + +void SmCmdBoxWindow::ShowContextMenu(const Point& rPos) +{ + ToTop(); + SmViewShell *pViewSh = GetView(); + if (pViewSh) + pViewSh->GetViewFrame().GetDispatcher()->ExecutePopup("edit", this, &rPos); +} + +void SmCmdBoxWindow::Command(const CommandEvent& rCEvt) +{ + if (rCEvt.GetCommand() == CommandEventId::ContextMenu) + { + ShowContextMenu(rCEvt.GetMousePosPixel()); + return; + } + + SfxDockingWindow::Command(rCEvt); +} + +SmCmdBoxWindow::~SmCmdBoxWindow () +{ + disposeOnce(); +} + +void SmCmdBoxWindow::dispose() +{ + aInitialFocusTimer.Stop(); + bExiting = true; + aController.dispose(); + m_xEdit.reset(); + SfxDockingWindow::dispose(); +} + +SmViewShell * SmCmdBoxWindow::GetView() +{ + SfxDispatcher *pDispatcher = GetBindings().GetDispatcher(); + SfxViewShell *pView = pDispatcher ? pDispatcher->GetFrame()->GetViewShell() : nullptr; + return dynamic_cast<SmViewShell*>( pView); +} + +Size SmCmdBoxWindow::CalcDockingSize(SfxChildAlignment eAlign) +{ + switch (eAlign) + { + case SfxChildAlignment::LEFT: + case SfxChildAlignment::RIGHT: + return Size(); + default: + break; + } + return SfxDockingWindow::CalcDockingSize(eAlign); +} + +SfxChildAlignment SmCmdBoxWindow::CheckAlignment(SfxChildAlignment eActual, + SfxChildAlignment eWish) +{ + switch (eWish) + { + case SfxChildAlignment::TOP: + case SfxChildAlignment::BOTTOM: + case SfxChildAlignment::NOALIGNMENT: + return eWish; + default: + break; + } + + return eActual; +} + +void SmCmdBoxWindow::StateChanged( StateChangedType nStateChange ) +{ + if (StateChangedType::InitShow == nStateChange) + { + Resize(); // avoid SmEditWindow not being painted correctly + + // set initial position of window in floating mode + if (IsFloatingMode()) + AdjustPosition(); //! don't change pos in docking-mode ! + + aInitialFocusTimer.Start(); + } + + SfxDockingWindow::StateChanged( nStateChange ); +} + +IMPL_LINK_NOARG( SmCmdBoxWindow, InitialFocusTimerHdl, Timer *, void ) +{ + // We want to have the focus in the edit window once Math has been opened + // to allow for immediate typing. + // Problem: There is no proper way to do this + // Thus: this timer based solution has been implemented (see GrabFocus below) + + // Follow-up problem (#i114910): grabbing the focus may bust the help system since + // it relies on getting the current frame which conflicts with grabbing the focus. + // Thus aside from the 'GrabFocus' call everything else is to get the + // help reliably working despite using 'GrabFocus'. + + try + { + uno::Reference< frame::XDesktop2 > xDesktop = frame::Desktop::create( comphelper::getProcessComponentContext() ); + + m_xEdit->GrabFocus(); + + SmViewShell* pView = GetView(); + assert(pView); + bool bInPlace = pView->GetViewFrame().GetFrame().IsInPlace(); + uno::Reference< frame::XFrame > xFrame( GetBindings().GetDispatcher()->GetFrame()->GetFrame().GetFrameInterface()); + if ( bInPlace ) + { + uno::Reference<container::XChild> xModel(pView->GetDoc()->GetModel(), + uno::UNO_QUERY_THROW); + uno::Reference< frame::XModel > xParent( xModel->getParent(), uno::UNO_QUERY_THROW ); + uno::Reference< frame::XController > xParentCtrler( xParent->getCurrentController() ); + uno::Reference< frame::XFramesSupplier > xParentFrame( xParentCtrler->getFrame(), uno::UNO_QUERY_THROW ); + xParentFrame->setActiveFrame( xFrame ); + } + else + { + xDesktop->setActiveFrame( xFrame ); + } + } + catch (uno::Exception &) + { + SAL_WARN( "starmath", "failed to properly set initial focus to edit window" ); + } +} + +void SmCmdBoxWindow::AdjustPosition() +{ + const tools::Rectangle aRect( Point(), GetParent()->GetOutputSizePixel() ); + Point aTopLeft( Point( aRect.Left(), + aRect.Bottom() - GetSizePixel().Height() ) ); + Point aPos( GetParent()->OutputToScreenPixel( aTopLeft ) ); + if (aPos.X() < 0) + aPos.setX( 0 ); + if (aPos.Y() < 0) + aPos.setY( 0 ); + SetPosPixel( aPos ); +} + +void SmCmdBoxWindow::ToggleFloatingMode() +{ + SfxDockingWindow::ToggleFloatingMode(); + + if (GetFloatingWindow()) + GetFloatingWindow()->SetMinOutputSizePixel(Size (200, 50)); +} + +void SmCmdBoxWindow::GetFocus() +{ + if (!bExiting) + m_xEdit->GrabFocus(); +} + +SFX_IMPL_DOCKINGWINDOW_WITHID(SmCmdBoxWrapper, SID_CMDBOXWINDOW); + +SmCmdBoxWrapper::SmCmdBoxWrapper(vcl::Window *pParentWindow, sal_uInt16 nId, + SfxBindings *pBindings, + SfxChildWinInfo *pInfo) : + SfxChildWindow(pParentWindow, nId) +{ + VclPtrInstance<SmCmdBoxWindow> pDialog(pBindings, this, pParentWindow); + SetWindow(pDialog); + // make window docked to the bottom initially (after first start) + SetAlignment(SfxChildAlignment::BOTTOM); + pDialog->setDeferredProperties(); + pDialog->set_border_width(CMD_BOX_PADDING); + pDialog->set_margin_top(CMD_BOX_PADDING_TOP); + pDialog->Initialize(pInfo); +} + +SFX_IMPL_SUPERCLASS_INTERFACE(SmViewShell, SfxViewShell) + +void SmViewShell::InitInterface_Impl() +{ + GetStaticInterface()->RegisterObjectBar(SFX_OBJECTBAR_TOOLS, + SfxVisibilityFlags::Standard | SfxVisibilityFlags::FullScreen | SfxVisibilityFlags::Server, + ToolbarId::Math_Toolbox); + //Dummy-Objectbar, to avoid quiver while activating + + GetStaticInterface()->RegisterChildWindow(SmCmdBoxWrapper::GetChildWindowId()); + GetStaticInterface()->RegisterChildWindow(SfxInfoBarContainerChild::GetChildWindowId()); + + GetStaticInterface()->RegisterChildWindow(::sfx2::sidebar::SidebarChildWindow::GetChildWindowId()); +} + +SFX_IMPL_NAMED_VIEWFACTORY(SmViewShell, "Default") +{ + SFX_VIEW_REGISTRATION(SmDocShell); +} + +void SmViewShell::InnerResizePixel(const Point &rOfs, const Size &rSize, bool) +{ + Size aObjSize = GetObjectShell()->GetVisArea().GetSize(); + if ( !aObjSize.IsEmpty() ) + { + Size aProvidedSize = GetWindow()->PixelToLogic(rSize, MapMode(SmMapUnit())); + Fraction aZoomX(aProvidedSize.Width(), aObjSize.Width()); + Fraction aZoomY(aProvidedSize.Height(), aObjSize.Height()); + MapMode aMap(mxGraphicWindow->GetGraphicMapMode()); + aMap.SetScaleX(aZoomX); + aMap.SetScaleY(aZoomY); + mxGraphicWindow->SetGraphicMapMode(aMap); + } + + SetBorderPixel( SvBorder() ); + mxGraphicWindow->SetPosSizePixel(rOfs, rSize); + GetGraphicWidget().SetTotalSize(); +} + +void SmViewShell::OuterResizePixel(const Point &rOfs, const Size &rSize) +{ + mxGraphicWindow->SetPosSizePixel(rOfs, rSize); + if (GetDoc()->IsPreview()) + mxGraphicWindow->ZoomToFitInWindow(); +} + +void SmViewShell::QueryObjAreaPixel( tools::Rectangle& rRect ) const +{ + rRect.SetSize(mxGraphicWindow->GetSizePixel()); +} + +void SmViewShell::SetZoomFactor( const Fraction &rX, const Fraction &rY ) +{ + const Fraction &rFrac = std::min(rX, rY); + mxGraphicWindow->SetZoom(sal::static_int_cast<sal_uInt16>(tools::Long(rFrac * Fraction( 100, 1 )))); + + //To avoid rounding errors base class regulates crooked values too + //if necessary + SfxViewShell::SetZoomFactor( rX, rY ); +} + +SfxPrinter* SmViewShell::GetPrinter(bool bCreate) +{ + SmDocShell* pDoc = GetDoc(); + if (pDoc->HasPrinter() || bCreate) + return pDoc->GetPrinter(); + return nullptr; +} + +sal_uInt16 SmViewShell::SetPrinter(SfxPrinter *pNewPrinter, SfxPrinterChangeFlags nDiffFlags ) +{ + SfxPrinter *pOld = GetDoc()->GetPrinter(); + if ( pOld && pOld->IsPrinting() ) + return SFX_PRINTERROR_BUSY; + + if ((nDiffFlags & SfxPrinterChangeFlags::PRINTER) == SfxPrinterChangeFlags::PRINTER) + GetDoc()->SetPrinter( pNewPrinter ); + + if ((nDiffFlags & SfxPrinterChangeFlags::OPTIONS) == SfxPrinterChangeFlags::OPTIONS) + { + SmModule *pp = SM_MOD(); + pp->GetConfig()->ItemSetToConfig(pNewPrinter->GetOptions()); + } + return 0; +} + +bool SmViewShell::HasPrintOptionsPage() const +{ + return true; +} + +std::unique_ptr<SfxTabPage> SmViewShell::CreatePrintOptionsPage(weld::Container* pPage, weld::DialogController* pController, + const SfxItemSet &rOptions) +{ + return SmPrintOptionsTabPage::Create(pPage, pController, rOptions); +} + +SmEditWindow *SmViewShell::GetEditWindow() +{ + SmCmdBoxWrapper* pWrapper = static_cast<SmCmdBoxWrapper*>( + GetViewFrame().GetChildWindow(SmCmdBoxWrapper::GetChildWindowId())); + + if (pWrapper != nullptr) + { + SmEditWindow& rEditWin = pWrapper->GetEditWindow(); + return &rEditWin; + } + + return nullptr; +} + +void SmViewShell::SetStatusText(const OUString& rText) +{ + maStatusText = rText; + GetViewFrame().GetBindings().Invalidate(SID_TEXTSTATUS); +} + +void SmViewShell::ShowError(const SmErrorDesc* pErrorDesc) +{ + assert(GetDoc()); + if (pErrorDesc || nullptr != (pErrorDesc = GetDoc()->GetParser()->GetError()) ) + { + SetStatusText( pErrorDesc->m_aText ); + if (SmEditWindow* pEdit = GetEditWindow()) + pEdit->MarkError( Point( pErrorDesc->m_pNode->GetColumn(), + pErrorDesc->m_pNode->GetRow())); + } +} + +void SmViewShell::NextError() +{ + assert(GetDoc()); + const SmErrorDesc *pErrorDesc = GetDoc()->GetParser()->NextError(); + + if (pErrorDesc) + ShowError( pErrorDesc ); +} + +void SmViewShell::PrevError() +{ + assert(GetDoc()); + const SmErrorDesc *pErrorDesc = GetDoc()->GetParser()->PrevError(); + + if (pErrorDesc) + ShowError( pErrorDesc ); +} + +void SmViewShell::Insert( SfxMedium& rMedium ) +{ + SmDocShell *pDoc = GetDoc(); + bool bRet = false; + + uno::Reference <embed::XStorage> xStorage = rMedium.GetStorage(); + if (xStorage.is() && xStorage->getElementNames().hasElements()) + { + if (xStorage->hasByName("content.xml")) + { + // is this a fabulous math package ? + rtl::Reference<SmModel> xModel(dynamic_cast<SmModel*>(pDoc->GetModel().get())); + SmXMLImportWrapper aEquation(xModel); //!! modifies the result of pDoc->GetText() !! + bRet = ERRCODE_NONE == aEquation.Import(rMedium); + } + } + + if (!bRet) + return; + + OUString aText = pDoc->GetText(); + if (SmEditWindow *pEditWin = GetEditWindow()) + pEditWin->InsertText( aText ); + else + { + SAL_WARN( "starmath", "EditWindow missing" ); + } + + pDoc->Parse(); + pDoc->SetModified(); + + SfxBindings &rBnd = GetViewFrame().GetBindings(); + rBnd.Invalidate(SID_GRAPHIC_SM); + rBnd.Invalidate(SID_TEXT); +} + +void SmViewShell::InsertFrom(SfxMedium &rMedium) +{ + bool bSuccess = false; + SmDocShell* pDoc = GetDoc(); + SvStream* pStream = rMedium.GetInStream(); + + if (pStream) + { + const OUString& rFltName = rMedium.GetFilter()->GetFilterName(); + if ( rFltName == MATHML_XML ) + { + rtl::Reference<SmModel> xModel(dynamic_cast<SmModel*>(pDoc->GetModel().get())); + SmXMLImportWrapper aEquation(xModel); //!! modifies the result of pDoc->GetText() !! + bSuccess = ERRCODE_NONE == aEquation.Import(rMedium); + } + } + + if (!bSuccess) + return; + + OUString aText = pDoc->GetText(); + if (SmEditWindow *pEditWin = GetEditWindow()) + pEditWin->InsertText(aText); + else + SAL_WARN( "starmath", "EditWindow missing" ); + + pDoc->Parse(); + pDoc->SetModified(); + + SfxBindings& rBnd = GetViewFrame().GetBindings(); + rBnd.Invalidate(SID_GRAPHIC_SM); + rBnd.Invalidate(SID_TEXT); +} + +void SmViewShell::Execute(SfxRequest& rReq) +{ + SmEditWindow *pWin = GetEditWindow(); + + switch (rReq.GetSlot()) + { + case SID_FORMULACURSOR: + { + SmModule *pp = SM_MOD(); + + const SfxItemSet *pArgs = rReq.GetArgs(); + const SfxPoolItem *pItem; + + bool bVal; + if ( pArgs && + SfxItemState::SET == pArgs->GetItemState( SID_FORMULACURSOR, false, &pItem)) + bVal = static_cast<const SfxBoolItem *>(pItem)->GetValue(); + else + bVal = !pp->GetConfig()->IsShowFormulaCursor(); + + pp->GetConfig()->SetShowFormulaCursor(bVal); + if (!IsInlineEditEnabled()) + GetGraphicWidget().ShowCursor(bVal); + break; + } + case SID_DRAW: + if (pWin) + { + GetDoc()->SetText( pWin->GetText() ); + SetStatusText(OUString()); + ShowError( nullptr ); + GetDoc()->Repaint(); + } + break; + + case SID_ZOOM_OPTIMAL: + mxGraphicWindow->ZoomToFitInWindow(); + break; + + case SID_ZOOMIN: + mxGraphicWindow->SetZoom(mxGraphicWindow->GetZoom() + 25); + break; + + case SID_ZOOMOUT: + SAL_WARN_IF( mxGraphicWindow->GetZoom() < 25, "starmath", "incorrect sal_uInt16 argument" ); + mxGraphicWindow->SetZoom(mxGraphicWindow->GetZoom() - 25); + break; + + case SID_COPYOBJECT: + { + //TODO/LATER: does not work because of UNO Tunneling - will be fixed later + Reference< datatransfer::XTransferable > xTrans( GetDoc()->GetModel(), uno::UNO_QUERY ); + if( xTrans.is() ) + { + auto pTrans = dynamic_cast<TransferableHelper*>(xTrans.get()); + if (pTrans) + { + if (pWin) + pTrans->CopyToClipboard(pWin->GetClipboard()); + } + } + } + break; + + case SID_PASTEOBJECT: + { + uno::Reference < io::XInputStream > xStrm; + if (pWin) + { + TransferableDataHelper aData(TransferableDataHelper::CreateFromClipboard(pWin->GetClipboard())); + SotClipboardFormatId nId; + if( aData.GetTransferable().is() && + ( aData.HasFormat( nId = SotClipboardFormatId::EMBEDDED_OBJ ) || + (aData.HasFormat( SotClipboardFormatId::OBJECTDESCRIPTOR ) && + aData.HasFormat( nId = SotClipboardFormatId::EMBED_SOURCE )))) + xStrm = aData.GetInputStream(nId, OUString()); + } + + if (xStrm.is()) + { + try + { + uno::Reference < embed::XStorage > xStorage = + ::comphelper::OStorageHelper::GetStorageFromInputStream( xStrm, ::comphelper::getProcessComponentContext() ); + SfxMedium aMedium( xStorage, OUString() ); + Insert( aMedium ); + GetDoc()->UpdateText(); + } + catch (uno::Exception &) + { + SAL_WARN( "starmath", "SmViewShell::Execute (SID_PASTEOBJECT): failed to get storage from input stream" ); + } + } + } + break; + + + case SID_CUT: + if (IsInlineEditEnabled()) + { + GetDoc()->GetCursor().Cut(&GetGraphicWindow()); + GetGraphicWidget().GrabFocus(); + } + else if (pWin) + pWin->Cut(); + break; + + case SID_COPY: + if (IsInlineEditEnabled()) + { + GetDoc()->GetCursor().Copy(&GetGraphicWindow()); + GetGraphicWidget().GrabFocus(); + } + else if (pWin) + { + if (pWin->IsAllSelected()) + { + GetViewFrame().GetDispatcher()->ExecuteList( + SID_COPYOBJECT, SfxCallMode::RECORD, + { new SfxVoidItem(SID_COPYOBJECT) }); + } + else + pWin->Copy(); + } + break; + + case SID_PASTE: + { + if (IsInlineEditEnabled()) + { + GetDoc()->GetCursor().Paste(&GetGraphicWindow()); + GetGraphicWidget().GrabFocus(); + break; + } + + bool bCallExec = nullptr == pWin; + if( !bCallExec ) + { + if (pWin) + { + TransferableDataHelper aDataHelper( + TransferableDataHelper::CreateFromClipboard( + pWin->GetClipboard())); + + if( aDataHelper.GetTransferable().is() && + aDataHelper.HasFormat( SotClipboardFormatId::STRING )) + pWin->Paste(); + else + bCallExec = true; + } + } + if( bCallExec ) + { + GetViewFrame().GetDispatcher()->ExecuteList( + SID_PASTEOBJECT, SfxCallMode::RECORD, + { new SfxVoidItem(SID_PASTEOBJECT) }); + } + } + break; + + case SID_DELETE: + if (IsInlineEditEnabled()) + { + if (!GetDoc()->GetCursor().HasSelection()) + { + GetDoc()->GetCursor().Move(&GetGraphicWindow().GetGraphicWidget().GetOutputDevice(), MoveRight, false); + if (!GetDoc()->GetCursor().HasComplexSelection()) + GetDoc()->GetCursor().Delete(); + } + else + GetDoc()->GetCursor().Delete(); + GetGraphicWidget().GrabFocus(); + } + else if (pWin) + pWin->Delete(); + break; + + case SID_SELECT: + if (pWin) + pWin->SelectAll(); + break; + + case SID_INSERTCOMMANDTEXT: + { + const SfxStringItem& rItem = rReq.GetArgs()->Get(SID_INSERTCOMMANDTEXT); + + if (IsInlineEditEnabled()) + { + GetDoc()->GetCursor().InsertCommandText(rItem.GetValue()); + GetGraphicWidget().GrabFocus(); + } + else if (pWin) + { + pWin->InsertText(rItem.GetValue()); + } + break; + + } + + case SID_INSERTSPECIAL: + { + const SfxStringItem& rItem = rReq.GetArgs()->Get(SID_INSERTSPECIAL); + + if (IsInlineEditEnabled()) + GetDoc()->GetCursor().InsertSpecial(rItem.GetValue()); + else if (pWin) + pWin->InsertText(rItem.GetValue()); + break; + } + + case SID_IMPORT_FORMULA: + { + mpRequest.reset(new SfxRequest( rReq )); + mpDocInserter.reset(new ::sfx2::DocumentInserter(pWin ? pWin->GetFrameWeld() : nullptr, + GetDoc()->GetFactory().GetFactoryName())); + mpDocInserter->StartExecuteModal( LINK( this, SmViewShell, DialogClosedHdl ) ); + break; + } + + case SID_IMPORT_MATHML_CLIPBOARD: + { + if (pWin) + { + TransferableDataHelper aDataHelper(TransferableDataHelper::CreateFromClipboard(pWin->GetClipboard())); + uno::Reference < io::XInputStream > xStrm; + if ( aDataHelper.GetTransferable().is() ) + { + SotClipboardFormatId nId = SotClipboardFormatId::MATHML; + if (aDataHelper.HasFormat(nId)) + { + xStrm = aDataHelper.GetInputStream(nId, ""); + if (xStrm.is()) + { + SfxMedium aClipboardMedium; + aClipboardMedium.GetItemSet(); //generate initial itemset, not sure if necessary + std::shared_ptr<const SfxFilter> pMathFilter = + SfxFilter::GetFilterByName(MATHML_XML); + aClipboardMedium.SetFilter(pMathFilter); + aClipboardMedium.setStreamToLoadFrom(xStrm, true /*bIsReadOnly*/); + InsertFrom(aClipboardMedium); + GetDoc()->UpdateText(); + } + } + else + { + nId = SotClipboardFormatId::STRING; + if (aDataHelper.HasFormat(nId)) + { + // In case of FORMAT_STRING no stream exists, need to generate one + OUString aString; + if (aDataHelper.GetString( nId, aString)) + { + // tdf#117091 force xml declaration to exist + if (!aString.startsWith("<?xml")) + aString = "<?xml version=\"1.0\"?>\n" + aString; + + SfxMedium aClipboardMedium; + aClipboardMedium.GetItemSet(); //generates initial itemset, not sure if necessary + std::shared_ptr<const SfxFilter> pMathFilter = + SfxFilter::GetFilterByName(MATHML_XML); + aClipboardMedium.SetFilter(pMathFilter); + + SvMemoryStream aStrm( const_cast<sal_Unicode *>(aString.getStr()), aString.getLength() * sizeof(sal_Unicode), StreamMode::READ); + uno::Reference<io::XInputStream> xStrm2( new ::utl::OInputStreamWrapper(aStrm) ); + aClipboardMedium.setStreamToLoadFrom(xStrm2, true /*bIsReadOnly*/); + InsertFrom(aClipboardMedium); + GetDoc()->UpdateText(); + } + } + } + } + } + break; + } + + case SID_NEXTERR: + NextError(); + if (pWin) + pWin->GrabFocus(); + break; + + case SID_PREVERR: + PrevError(); + if (pWin) + pWin->GrabFocus(); + break; + + case SID_NEXTMARK: + if (pWin) + { + pWin->SelNextMark(); + pWin->GrabFocus(); + } + break; + + case SID_PREVMARK: + if (pWin) + { + pWin->SelPrevMark(); + pWin->GrabFocus(); + } + break; + + case SID_TEXTSTATUS: + { + if (rReq.GetArgs() != nullptr) + { + const SfxStringItem& rItem = rReq.GetArgs()->Get(SID_TEXTSTATUS); + + SetStatusText(rItem.GetValue()); + } + + break; + } + + case SID_GETEDITTEXT: + if (pWin && !pWin->GetText().isEmpty()) + GetDoc()->SetText( pWin->GetText() ); + break; + + case SID_ATTR_ZOOM: + { + if ( !GetViewFrame().GetFrame().IsInPlace() ) + { + const SfxItemSet *pSet = rReq.GetArgs(); + if ( pSet ) + { + ZoomByItemSet(pSet); + } + else + { + SfxItemSetFixed<SID_ATTR_ZOOM, SID_ATTR_ZOOM> aSet( SmDocShell::GetPool() ); + aSet.Put( SvxZoomItem( SvxZoomType::PERCENT, mxGraphicWindow->GetZoom())); + SvxAbstractDialogFactory* pFact = SvxAbstractDialogFactory::Create(); + ScopedVclPtr<AbstractSvxZoomDialog> xDlg(pFact->CreateSvxZoomDialog(GetViewFrame().GetWindow().GetFrameWeld(), aSet)); + xDlg->SetLimits( MINZOOM, MAXZOOM ); + if (xDlg->Execute() != RET_CANCEL) + ZoomByItemSet(xDlg->GetOutputItemSet()); + } + } + } + break; + + case SID_ATTR_ZOOMSLIDER: + { + const SfxItemSet *pArgs = rReq.GetArgs(); + const SfxPoolItem* pItem; + + if ( pArgs && SfxItemState::SET == pArgs->GetItemState(SID_ATTR_ZOOMSLIDER, true, &pItem ) ) + { + const sal_uInt16 nCurrentZoom = static_cast<const SvxZoomSliderItem *>(pItem)->GetValue(); + mxGraphicWindow->SetZoom(nCurrentZoom); + } + } + break; + + case SID_ELEMENTSDOCKINGWINDOW: + { + // First make sure that the sidebar is visible + GetViewFrame().ShowChildWindow(SID_SIDEBAR); + + sfx2::sidebar::Sidebar::TogglePanel(u"MathElementsPanel", + GetViewFrame().GetFrame().GetFrameInterface()); + GetViewFrame().GetBindings().Invalidate( SID_ELEMENTSDOCKINGWINDOW ); + + rReq.Ignore (); + } + break; + + case SID_CMDBOXWINDOW: + { + GetViewFrame().ToggleChildWindow(SID_CMDBOXWINDOW); + GetViewFrame().GetBindings().Invalidate(SID_CMDBOXWINDOW); + } + break; + + case SID_UNICODE_NOTATION_TOGGLE: + { + EditEngine* pEditEngine = nullptr; + if( pWin ) + pEditEngine = pWin->GetEditEngine(); + + EditView* pEditView = nullptr; + if( pEditEngine ) + pEditView = pEditEngine->GetView(); + + if( pEditView ) + { + const OUString sInput = pEditView->GetSurroundingText(); + ESelection aSel( pWin->GetSelection() ); + + if ( aSel.nStartPos > aSel.nEndPos ) + aSel.nEndPos = aSel.nStartPos; + + //calculate a valid end-position by reading logical characters + sal_Int32 nUtf16Pos=0; + while( (nUtf16Pos < sInput.getLength()) && (nUtf16Pos < aSel.nEndPos) ) + { + sInput.iterateCodePoints(&nUtf16Pos); + if( nUtf16Pos > aSel.nEndPos ) + aSel.nEndPos = nUtf16Pos; + } + + ToggleUnicodeCodepoint aToggle; + while( nUtf16Pos && aToggle.AllowMoreInput( sInput[nUtf16Pos-1]) ) + --nUtf16Pos; + const OUString sReplacement = aToggle.ReplacementString(); + if( !sReplacement.isEmpty() ) + { + pEditView->SetSelection( aSel ); + pEditEngine->UndoActionStart(EDITUNDO_REPLACEALL); + aSel.nStartPos = aSel.nEndPos - aToggle.StringToReplace().getLength(); + pWin->SetSelection( aSel ); + pEditView->InsertText( sReplacement, true ); + pEditEngine->UndoActionEnd(); + pWin->Flush(); + } + } + } + break; + + case SID_SYMBOLS_CATALOGUE: + { + + // get device used to retrieve the FontList + SmDocShell *pDoc = GetDoc(); + OutputDevice *pDev = pDoc->GetPrinter(); + if (!pDev || pDev->GetFontFaceCollectionCount() == 0) + pDev = &SM_MOD()->GetDefaultVirtualDev(); + SAL_WARN_IF( !pDev, "starmath", "device for font list missing" ); + + SmModule *pp = SM_MOD(); + SmSymbolDialog aDialog(pWin ? pWin->GetFrameWeld() : nullptr, pDev, pp->GetSymbolManager(), *this); + aDialog.run(); + } + break; + + case SID_CHARMAP: + { + const SfxItemSet* pArgs = rReq.GetArgs(); + const SfxStringItem* pItem = nullptr; + if (pArgs && SfxItemState::SET == pArgs->GetItemState(SID_CHARMAP, true, &pItem)) + { + if (IsInlineEditEnabled()) + GetDoc()->GetCursor().InsertText(pItem->GetValue()); + else if (pWin) + pWin->InsertText(pItem->GetValue()); + break; + } + + SvxAbstractDialogFactory* pFact = SvxAbstractDialogFactory::Create(); + SfxAllItemSet aSet(GetViewFrame().GetObjectShell()->GetPool()); + aSet.Put(SfxBoolItem(FN_PARAM_1, false)); + aSet.Put(SfxStringItem(SID_FONT_NAME, + GetDoc()->GetFormat().GetFont(FNT_VARIABLE).GetFamilyName())); + ScopedVclPtr<SfxAbstractDialog> pDialog( + pFact->CreateCharMapDialog(pWin ? pWin->GetFrameWeld() : nullptr, aSet, + GetViewFrame().GetFrame().GetFrameInterface())); + pDialog->Execute(); + } + break; + + case SID_ATTR_PARA_LEFT_TO_RIGHT: + case SID_ATTR_PARA_RIGHT_TO_LEFT: + { + bool bRTL = rReq.GetSlot() == SID_ATTR_PARA_RIGHT_TO_LEFT; + GetDoc()->SetRightToLeft(bRTL); + GetGraphicWindow().GetGraphicWidget().GetOutputDevice().EnableRTL(bRTL); + GetViewFrame().GetBindings().Invalidate(bRTL ? SID_ATTR_PARA_LEFT_TO_RIGHT : SID_ATTR_PARA_RIGHT_TO_LEFT); + } + break; + } + rReq.Done(); +} + + +void SmViewShell::GetState(SfxItemSet &rSet) +{ + SfxWhichIter aIter(rSet); + + SmEditWindow *pEditWin = GetEditWindow(); + for (sal_uInt16 nWh = aIter.FirstWhich(); nWh != 0; nWh = aIter.NextWhich()) + { + switch (nWh) + { + case SID_CUT: + case SID_COPY: + case SID_DELETE: + if (IsInlineEditEnabled()) + { + if (!GetDoc()->GetCursor().HasSelection()) + rSet.DisableItem(nWh); + } + else if (! pEditWin || ! pEditWin->IsSelected()) + rSet.DisableItem(nWh); + break; + + case SID_PASTE: + if (pEditWin) + { + TransferableDataHelper aDataHelper( + TransferableDataHelper::CreateFromClipboard( + pEditWin->GetClipboard()) ); + + mbPasteState = aDataHelper.GetTransferable().is() && + ( aDataHelper.HasFormat( SotClipboardFormatId::STRING ) || + aDataHelper.HasFormat( SotClipboardFormatId::EMBEDDED_OBJ ) || + (aDataHelper.HasFormat( SotClipboardFormatId::OBJECTDESCRIPTOR ) + && aDataHelper.HasFormat( SotClipboardFormatId::EMBED_SOURCE ))); + } + if( !mbPasteState ) + rSet.DisableItem( nWh ); + break; + + case SID_ATTR_ZOOM: + rSet.Put(SvxZoomItem( SvxZoomType::PERCENT, mxGraphicWindow->GetZoom())); + [[fallthrough]]; + case SID_ZOOMIN: + case SID_ZOOMOUT: + case SID_ZOOM_OPTIMAL: + if ( GetViewFrame().GetFrame().IsInPlace() ) + rSet.DisableItem( nWh ); + break; + + case SID_ATTR_ZOOMSLIDER : + { + const sal_uInt16 nCurrentZoom = mxGraphicWindow->GetZoom(); + SvxZoomSliderItem aZoomSliderItem( nCurrentZoom, MINZOOM, MAXZOOM ); + aZoomSliderItem.AddSnappingPoint( 100 ); + rSet.Put( aZoomSliderItem ); + } + break; + + case SID_NEXTERR: + case SID_PREVERR: + case SID_NEXTMARK: + case SID_PREVMARK: + case SID_DRAW: + case SID_SELECT: + if (! pEditWin || pEditWin->IsEmpty()) + rSet.DisableItem(nWh); + break; + + case SID_TEXTSTATUS: + { + rSet.Put(SfxStringItem(nWh, maStatusText)); + } + break; + + case SID_FORMULACURSOR: + { + if (IsInlineEditEnabled()) + rSet.DisableItem(nWh); + else + rSet.Put(SfxBoolItem(nWh, SM_MOD()->GetConfig()->IsShowFormulaCursor())); + } + break; + case SID_ELEMENTSDOCKINGWINDOW: + { + const bool bState = sfx2::sidebar::Sidebar::IsPanelVisible( + u"MathElementsPanel", GetViewFrame().GetFrame().GetFrameInterface()); + rSet.Put(SfxBoolItem(SID_ELEMENTSDOCKINGWINDOW, bState)); + } + break; + case SID_CMDBOXWINDOW: + { + bool bState = false; + auto pCmdWin = GetViewFrame().GetChildWindow(SID_CMDBOXWINDOW); + if (pCmdWin) + bState = pCmdWin->IsVisible(); + rSet.Put(SfxBoolItem(SID_CMDBOXWINDOW, bState)); + } + break; + case SID_ATTR_PARA_LEFT_TO_RIGHT: + rSet.Put(SfxBoolItem(nWh, !GetDoc()->GetFormat().IsRightToLeft())); + break; + + case SID_ATTR_PARA_RIGHT_TO_LEFT: + rSet.Put(SfxBoolItem(nWh, GetDoc()->GetFormat().IsRightToLeft())); + break; + } + } +} + +namespace +{ +css::uno::Reference<css::ui::XSidebar> +getSidebarFromModel(const css::uno::Reference<css::frame::XModel>& xModel) +{ + css::uno::Reference<css::container::XChild> xChild(xModel, css::uno::UNO_QUERY); + if (!xChild.is()) + return nullptr; + css::uno::Reference<css::frame::XModel> xParent(xChild->getParent(), css::uno::UNO_QUERY); + if (!xParent.is()) + return nullptr; + css::uno::Reference<css::frame::XController2> xController(xParent->getCurrentController(), + css::uno::UNO_QUERY); + if (!xController.is()) + return nullptr; + css::uno::Reference<css::ui::XSidebarProvider> xSidebarProvider = xController->getSidebar(); + if (!xSidebarProvider.is()) + return nullptr; + return xSidebarProvider->getSidebar(); +} +class SmController : public SfxBaseController +{ +public: + SmController(SfxViewShell& rViewShell) + : SfxBaseController(&rViewShell) + , mpSelectionChangeHandler(new svx::sidebar::SelectionChangeHandler( + GetContextName, this, vcl::EnumContext::Context::Math)) + { + rViewShell.SetContextName(GetContextName()); + } + // No need to call mpSelectionChangeHandler->Disconnect() unless SmController implements XSelectionSupplier + // ~SmController() { mpSelectionChangeHandler->Disconnect(); } + + // css::frame::XController + void SAL_CALL attachFrame(const css::uno::Reference<css::frame::XFrame>& xFrame) override + { + SfxBaseController::attachFrame(xFrame); + + if (comphelper::LibreOfficeKit::isActive()) + { + CopyLokViewCallbackFromFrameCreator(); + // In lok mode, DocumentHolder::ShowUI is not called on OLE in-place activation, + // because respective code is skipped in OCommonEmbeddedObject::SwitchStateTo_Impl, + // so sidebar controller does not get registered properly; do it here + if (auto xSidebar = getSidebarFromModel(getModel())) + { + auto pSidebar = dynamic_cast<sfx2::sidebar::SidebarController*>(xSidebar.get()); + assert(pSidebar); + pSidebar->registerSidebarForFrame(this); + pSidebar->updateModel(getModel()); + } + } + + // No need to call mpSelectionChangeHandler->Connect() unless SmController implements XSelectionSupplier + mpSelectionChangeHandler->selectionChanged({}); // Installs the correct context + } + + virtual void SAL_CALL dispose() override + { + if (comphelper::LibreOfficeKit::isActive()) + if (auto pViewShell = GetViewShell_Impl()) + pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_CURSOR_VISIBLE, + OString::boolean(false)); + + SfxBaseController::dispose(); + } + +private: + static OUString GetContextName() { return "Math"; } // Static constant for now + + rtl::Reference<svx::sidebar::SelectionChangeHandler> mpSelectionChangeHandler; +}; +} + +SmViewShell::SmViewShell(SfxViewFrame& rFrame_, SfxViewShell *) + : SfxViewShell(rFrame_, SfxViewShellFlags::HAS_PRINTOPTIONS) + , mxGraphicWindow(VclPtr<SmGraphicWindow>::Create(*this)) + , maGraphicController(mxGraphicWindow->GetGraphicWidget(), SID_GRAPHIC_SM, rFrame_.GetBindings()) + , mbPasteState(false) +{ + SetStatusText(OUString()); + SetWindow(mxGraphicWindow.get()); + SfxShell::SetName("SmView"); + SfxShell::SetUndoManager( &GetDoc()->GetEditEngine().GetUndoManager() ); + SetController(new SmController(*this)); +} + +SmViewShell::~SmViewShell() +{ + //!! this view shell is not active anymore !! + // Thus 'SmGetActiveView' will give a 0 pointer. + // Thus we need to supply this view as argument + if (SmEditWindow *pEditWin = GetEditWindow()) + pEditWin->DeleteEditView(); + mxGraphicWindow.disposeAndClear(); +} + +void SmViewShell::Deactivate( bool bIsMDIActivate ) +{ + if (SmEditWindow *pEdit = GetEditWindow()) + pEdit->Flush(); + + SfxViewShell::Deactivate( bIsMDIActivate ); +} + +void SmViewShell::Activate( bool bIsMDIActivate ) +{ + SfxViewShell::Activate( bIsMDIActivate ); + + if (IsInlineEditEnabled()) + { + // In LOK, activate in-place editing + GetGraphicWidget().GrabFocus(); + } + else if (SmEditWindow *pEdit = GetEditWindow()) + { + //! Since there is no way to be informed if a "drag and drop" + //! event has taken place, we call SetText here in order to + //! synchronize the GraphicWindow display with the text in the + //! EditEngine. + SmDocShell *pDoc = GetDoc(); + pDoc->SetText( pDoc->GetEditEngine().GetText() ); + + if ( bIsMDIActivate ) + pEdit->GrabFocus(); + } +} + +IMPL_LINK( SmViewShell, DialogClosedHdl, sfx2::FileDialogHelper*, _pFileDlg, void ) +{ + assert(_pFileDlg && "SmViewShell::DialogClosedHdl(): no file dialog"); + assert(mpDocInserter && "ScDocShell::DialogClosedHdl(): no document inserter"); + + if ( ERRCODE_NONE == _pFileDlg->GetError() ) + { + std::unique_ptr<SfxMedium> pMedium = mpDocInserter->CreateMedium(); + + if ( pMedium ) + { + if ( pMedium->IsStorage() ) + Insert( *pMedium ); + else + InsertFrom( *pMedium ); + pMedium.reset(); + + SmDocShell* pDoc = GetDoc(); + pDoc->UpdateText(); + pDoc->ArrangeFormula(); + pDoc->Repaint(); + // adjust window, repaint, increment ModifyCount,... + GetViewFrame().GetBindings().Invalidate(SID_GRAPHIC_SM); + } + } + + mpRequest->SetReturnValue( SfxBoolItem( mpRequest->GetSlot(), true ) ); + mpRequest->Done(); +} + +void SmViewShell::Notify( SfxBroadcaster& , const SfxHint& rHint ) +{ + switch( rHint.GetId() ) + { + case SfxHintId::ModeChanged: + case SfxHintId::DocChanged: + GetViewFrame().GetBindings().InvalidateAll(false); + break; + default: + break; + } +} + +bool SmViewShell::IsInlineEditEnabled() +{ + return comphelper::LibreOfficeKit::isActive() + || SM_MOD()->GetConfig()->IsInlineEditEnable(); +} + +void SmViewShell::StartMainHelp() +{ + Help* pHelp = Application::GetHelp(); + if (pHelp) + pHelp->Start(HID_SMA_MAIN_HELP, GetViewFrame().GetFrameWeld()); +} + +void SmViewShell::ZoomByItemSet(const SfxItemSet *pSet) +{ + assert(pSet); + const SvxZoomItem &rZoom = pSet->Get(SID_ATTR_ZOOM); + switch( rZoom.GetType() ) + { + case SvxZoomType::PERCENT: + mxGraphicWindow->SetZoom(sal::static_int_cast<sal_uInt16>(rZoom.GetValue ())); + break; + + case SvxZoomType::OPTIMAL: + mxGraphicWindow->ZoomToFitInWindow(); + break; + + case SvxZoomType::PAGEWIDTH: + case SvxZoomType::WHOLEPAGE: + { + const MapMode aMap( SmMapUnit() ); + SfxPrinter *pPrinter = GetPrinter( true ); + tools::Rectangle OutputRect(Point(), pPrinter->GetOutputSize()); + Size OutputSize(pPrinter->LogicToPixel(Size(OutputRect.GetWidth(), + OutputRect.GetHeight()), aMap)); + Size GraphicSize(pPrinter->LogicToPixel(GetDoc()->GetSize(), aMap)); + if (GraphicSize.Width() <= 0 || GraphicSize.Height() <= 0) + break; + sal_uInt16 nZ = std::min(o3tl::convert(OutputSize.Width(), 100, GraphicSize.Width()), + o3tl::convert(OutputSize.Height(), 100, GraphicSize.Height())); + mxGraphicWindow->SetZoom(nZ); + break; + } + default: + break; + } +} + +std::optional<OString> SmViewShell::getLOKPayload(int nType, int nViewId) const +{ + switch (nType) + { + case LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR: + { + OString sRectangle; + if (const SmGraphicWidget& widget = GetGraphicWidget(); widget.IsCursorVisible()) + { + SmCursor& rCursor = GetDoc()->GetCursor(); + OutputDevice& rOutDev = const_cast<SmGraphicWidget&>(widget).GetOutputDevice(); + tools::Rectangle aCaret = rCursor.GetCaretRectangle(rOutDev); + Point aFormulaDrawPos = widget.GetFormulaDrawPos(); + aCaret.Move(aFormulaDrawPos.X(), aFormulaDrawPos.Y()); + LokStarMathHelper helper(SfxViewShell::Current()); + tools::Rectangle aBounds = helper.GetBoundingBox(); + aCaret.Move(aBounds.Left(), aBounds.Top()); + sRectangle = aCaret.toString(); + } + return SfxLokHelper::makeVisCursorInvalidation(nViewId, sRectangle, false, {}); + } + case LOK_CALLBACK_TEXT_SELECTION: + { + OString sRectangle; + if (const SmGraphicWidget& widget = GetGraphicWidget(); widget.IsCursorVisible()) + { + SmCursor& rCursor = GetDoc()->GetCursor(); + OutputDevice& rOutDev = const_cast<SmGraphicWidget&>(widget).GetOutputDevice(); + tools::Rectangle aSelection = rCursor.GetSelectionRectangle(rOutDev); + if (!aSelection.IsEmpty()) + { + Point aFormulaDrawPos = widget.GetFormulaDrawPos(); + aSelection.Move(aFormulaDrawPos.X(), aFormulaDrawPos.Y()); + LokStarMathHelper helper(SfxViewShell::Current()); + tools::Rectangle aBounds = helper.GetBoundingBox(); + + aSelection.Move(aBounds.Left(), aBounds.Top()); + sRectangle = aSelection.toString(); + } + } + return sRectangle; + } + case LOK_CALLBACK_TEXT_SELECTION_START: + case LOK_CALLBACK_TEXT_SELECTION_END: + case LOK_CALLBACK_INVALIDATE_VIEW_CURSOR: + case LOK_CALLBACK_TEXT_VIEW_SELECTION: + return {}; + } + return SfxViewShell::getLOKPayload(nType, nViewId); // aborts +} + +void SmViewShell::SendCaretToLOK() const +{ + const int nViewId = sal_Int32(GetViewShellId()); + if (const auto& payload = getLOKPayload(LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR, nViewId)) + { + libreOfficeKitViewCallbackWithViewId(LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR, + *payload, nViewId); + } + if (const auto& payload = getLOKPayload(LOK_CALLBACK_TEXT_SELECTION, nViewId)) + { + libreOfficeKitViewCallback(LOK_CALLBACK_TEXT_SELECTION, *payload); + } +} + +void SmViewShell::InvalidateSlots() +{ + auto& rBind = GetViewFrame().GetBindings(); + rBind.Invalidate(SID_COPY); + rBind.Invalidate(SID_CUT); + rBind.Invalidate(SID_DELETE); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/visitors.cxx b/starmath/source/visitors.cxx new file mode 100644 index 0000000000..3e11201ebe --- /dev/null +++ b/starmath/source/visitors.cxx @@ -0,0 +1,2648 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <rtl/math.hxx> +#include <sal/log.hxx> +#include <tools/gen.hxx> +#include <vcl/lineinfo.hxx> +#include <visitors.hxx> +#include "tmpdevice.hxx" +#include <cursor.hxx> + +#include <starmathdatabase.hxx> + +// SmDefaultingVisitor + +void SmDefaultingVisitor::Visit( SmTableNode* pNode ) +{ + DefaultVisit( pNode ); +} + +void SmDefaultingVisitor::Visit( SmBraceNode* pNode ) +{ + DefaultVisit( pNode ); +} + +void SmDefaultingVisitor::Visit( SmBracebodyNode* pNode ) +{ + DefaultVisit( pNode ); +} + +void SmDefaultingVisitor::Visit( SmOperNode* pNode ) +{ + DefaultVisit( pNode ); +} + +void SmDefaultingVisitor::Visit( SmAlignNode* pNode ) +{ + DefaultVisit( pNode ); +} + +void SmDefaultingVisitor::Visit( SmAttributeNode* pNode ) +{ + DefaultVisit( pNode ); +} + +void SmDefaultingVisitor::Visit( SmFontNode* pNode ) +{ + DefaultVisit( pNode ); +} + +void SmDefaultingVisitor::Visit( SmUnHorNode* pNode ) +{ + DefaultVisit( pNode ); +} + +void SmDefaultingVisitor::Visit( SmBinHorNode* pNode ) +{ + DefaultVisit( pNode ); +} + +void SmDefaultingVisitor::Visit( SmBinVerNode* pNode ) +{ + DefaultVisit( pNode ); +} + +void SmDefaultingVisitor::Visit( SmBinDiagonalNode* pNode ) +{ + DefaultVisit( pNode ); +} + +void SmDefaultingVisitor::Visit( SmSubSupNode* pNode ) +{ + DefaultVisit( pNode ); +} + +void SmDefaultingVisitor::Visit( SmMatrixNode* pNode ) +{ + DefaultVisit( pNode ); +} + +void SmDefaultingVisitor::Visit( SmPlaceNode* pNode ) +{ + DefaultVisit( pNode ); +} + +void SmDefaultingVisitor::Visit( SmTextNode* pNode ) +{ + DefaultVisit( pNode ); +} + +void SmDefaultingVisitor::Visit( SmSpecialNode* pNode ) +{ + DefaultVisit( pNode ); +} + +void SmDefaultingVisitor::Visit( SmGlyphSpecialNode* pNode ) +{ + DefaultVisit( pNode ); +} + +void SmDefaultingVisitor::Visit( SmMathSymbolNode* pNode ) +{ + DefaultVisit( pNode ); +} + +void SmDefaultingVisitor::Visit( SmBlankNode* pNode ) +{ + DefaultVisit( pNode ); +} + +void SmDefaultingVisitor::Visit( SmErrorNode* pNode ) +{ + DefaultVisit( pNode ); +} + +void SmDefaultingVisitor::Visit( SmLineNode* pNode ) +{ + DefaultVisit( pNode ); +} + +void SmDefaultingVisitor::Visit( SmExpressionNode* pNode ) +{ + DefaultVisit( pNode ); +} + +void SmDefaultingVisitor::Visit( SmPolyLineNode* pNode ) +{ + DefaultVisit( pNode ); +} + +void SmDefaultingVisitor::Visit( SmRootNode* pNode ) +{ + DefaultVisit( pNode ); +} + +void SmDefaultingVisitor::Visit( SmRootSymbolNode* pNode ) +{ + DefaultVisit( pNode ); +} + +void SmDefaultingVisitor::Visit( SmRectangleNode* pNode ) +{ + DefaultVisit( pNode ); +} + +void SmDefaultingVisitor::Visit( SmVerticalBraceNode* pNode ) +{ + DefaultVisit( pNode ); +} + +// SmCaretLinesVisitor + +SmCaretLinesVisitor::SmCaretLinesVisitor(OutputDevice& rDevice, SmCaretPos position, Point offset) + : mrDev(rDevice) + , maPos(position) + , maOffset(offset) +{ +} + +void SmCaretLinesVisitor::DoIt() +{ + SAL_WARN_IF(!maPos.IsValid(), "starmath", "Cannot draw invalid position!"); + if (!maPos.IsValid()) + return; + + //Save device state + mrDev.Push( vcl::PushFlags::FONT | vcl::PushFlags::MAPMODE | vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR | vcl::PushFlags::TEXTCOLOR ); + + maPos.pSelectedNode->Accept( this ); + //Restore device state + mrDev.Pop( ); +} + +void SmCaretLinesVisitor::Visit( SmTextNode* pNode ) +{ + tools::Long i = maPos.nIndex; + + mrDev.SetFont( pNode->GetFont( ) ); + + //Find the line + SmNode* pLine = SmCursor::FindTopMostNodeInLine( pNode ); + + //Find coordinates + tools::Long left = pNode->GetLeft( ) + mrDev.GetTextWidth( pNode->GetText( ), 0, i ) + maOffset.X( ); + tools::Long top = pLine->GetTop( ) + maOffset.Y( ); + tools::Long height = pLine->GetHeight( ); + tools::Long left_line = pLine->GetLeft( ) + maOffset.X( ); + tools::Long right_line = pLine->GetRight( ) + maOffset.X( ); + + // Vertical line + ProcessCaretLine({ left, top }, { left, top + height }); + + // Underline + ProcessUnderline({ left_line, top + height }, { right_line, top + height }); +} + +void SmCaretLinesVisitor::DefaultVisit( SmNode* pNode ) +{ + //Find the line + SmNode* pLine = SmCursor::FindTopMostNodeInLine( pNode ); + + //Find coordinates + tools::Long left = pNode->GetLeft( ) + maOffset.X( ) + ( maPos.nIndex == 1 ? pNode->GetWidth( ) : 0 ); + tools::Long top = pLine->GetTop( ) + maOffset.Y( ); + tools::Long height = pLine->GetHeight( ); + tools::Long left_line = pLine->GetLeft( ) + maOffset.X( ); + tools::Long right_line = pLine->GetRight( ) + maOffset.X( ); + + // Vertical line + ProcessCaretLine({ left, top }, { left, top + height }); + + // Underline + ProcessUnderline({ left_line, top + height }, { right_line, top + height }); +} + +// SmCaretRectanglesVisitor + +SmCaretRectanglesVisitor::SmCaretRectanglesVisitor(OutputDevice& rDevice, SmCaretPos position) + : SmCaretLinesVisitor(rDevice, position, {}) +{ + DoIt(); +} + +void SmCaretRectanglesVisitor::ProcessCaretLine(Point from, Point to) { maCaret = { from, to }; } +void SmCaretRectanglesVisitor::ProcessUnderline(Point /*from*/, Point /*to*/) {} // No underline + +// SmCaretDrawingVisitor + +SmCaretDrawingVisitor::SmCaretDrawingVisitor( OutputDevice& rDevice, + SmCaretPos position, + Point offset, + bool caretVisible ) + : SmCaretLinesVisitor(rDevice, position, offset) + , mbCaretVisible( caretVisible ) +{ + DoIt(); +} + +void SmCaretDrawingVisitor::ProcessCaretLine(Point from, Point to) +{ + if ( mbCaretVisible ) { + //Set color + getDev().SetLineColor(COL_BLACK); + //Draw vertical line + getDev().DrawLine(from, to); + } +} + +void SmCaretDrawingVisitor::ProcessUnderline(Point from, Point to) +{ + //Set color + getDev().SetLineColor(COL_BLACK); + //Underline the line + getDev().DrawLine(from, to); +} + +// SmCaretPos2LineVisitor + +void SmCaretPos2LineVisitor::Visit( SmTextNode* pNode ) +{ + //Save device state + mpDev->Push( vcl::PushFlags::FONT | vcl::PushFlags::TEXTCOLOR ); + + tools::Long i = maPos.nIndex; + + mpDev->SetFont( pNode->GetFont( ) ); + + //Find coordinates + tools::Long left = pNode->GetLeft( ) + mpDev->GetTextWidth( pNode->GetText( ), 0, i ); + tools::Long top = pNode->GetTop( ); + tools::Long height = pNode->GetHeight( ); + + maLine = SmCaretLine( left, top, height ); + + //Restore device state + mpDev->Pop( ); +} + +void SmCaretPos2LineVisitor::DefaultVisit( SmNode* pNode ) +{ + //Vertical line ( code from SmCaretDrawingVisitor ) + Point p1 = pNode->GetTopLeft( ); + if( maPos.nIndex == 1 ) + p1.Move( pNode->GetWidth( ), 0 ); + + maLine = SmCaretLine( p1.X( ), p1.Y( ), pNode->GetHeight( ) ); +} + + +// SmDrawingVisitor + +void SmDrawingVisitor::Visit( SmTableNode* pNode ) +{ + DrawChildren( pNode ); +} + +void SmDrawingVisitor::Visit( SmBraceNode* pNode ) +{ + DrawChildren( pNode ); +} + +void SmDrawingVisitor::Visit( SmBracebodyNode* pNode ) +{ + DrawChildren( pNode ); +} + +void SmDrawingVisitor::Visit( SmOperNode* pNode ) +{ + DrawChildren( pNode ); +} + +void SmDrawingVisitor::Visit( SmAlignNode* pNode ) +{ + DrawChildren( pNode ); +} + +void SmDrawingVisitor::Visit( SmAttributeNode* pNode ) +{ + DrawChildren( pNode ); +} + +void SmDrawingVisitor::Visit( SmFontNode* pNode ) +{ + DrawChildren( pNode ); +} + +void SmDrawingVisitor::Visit( SmUnHorNode* pNode ) +{ + DrawChildren( pNode ); +} + +void SmDrawingVisitor::Visit( SmBinHorNode* pNode ) +{ + DrawChildren( pNode ); +} + +void SmDrawingVisitor::Visit( SmBinVerNode* pNode ) +{ + DrawChildren( pNode ); +} + +void SmDrawingVisitor::Visit( SmBinDiagonalNode* pNode ) +{ + DrawChildren( pNode ); +} + +void SmDrawingVisitor::Visit( SmSubSupNode* pNode ) +{ + DrawChildren( pNode ); +} + +void SmDrawingVisitor::Visit( SmMatrixNode* pNode ) +{ + DrawChildren( pNode ); +} + +void SmDrawingVisitor::Visit( SmPlaceNode* pNode ) +{ + DrawSpecialNode( pNode ); +} + +void SmDrawingVisitor::Visit( SmTextNode* pNode ) +{ + DrawTextNode( pNode ); +} + +void SmDrawingVisitor::Visit( SmSpecialNode* pNode ) +{ + DrawSpecialNode( pNode ); +} + +void SmDrawingVisitor::Visit( SmGlyphSpecialNode* pNode ) +{ + DrawSpecialNode( pNode ); +} + +void SmDrawingVisitor::Visit( SmMathSymbolNode* pNode ) +{ + DrawSpecialNode( pNode ); +} + +void SmDrawingVisitor::Visit( SmBlankNode* ) +{ +} + +void SmDrawingVisitor::Visit( SmErrorNode* pNode ) +{ + DrawSpecialNode( pNode ); +} + +void SmDrawingVisitor::Visit( SmLineNode* pNode ) +{ + DrawChildren( pNode ); +} + +void SmDrawingVisitor::Visit( SmExpressionNode* pNode ) +{ + DrawChildren( pNode ); +} + +void SmDrawingVisitor::Visit( SmRootNode* pNode ) +{ + DrawChildren( pNode ); +} + +void SmDrawingVisitor::Visit( SmVerticalBraceNode* pNode ) +{ + DrawChildren( pNode ); +} + +void SmDrawingVisitor::Visit( SmRootSymbolNode* pNode ) +{ + if ( pNode->IsPhantom( ) ) + return; + + // draw root-sign itself + DrawSpecialNode( pNode ); + + SmTmpDevice aTmpDev( mrDev, true ); + aTmpDev.SetFillColor( pNode->GetFont( ).GetColor( ) ); + mrDev.SetLineColor( ); + aTmpDev.SetFont( pNode->GetFont( ) ); + + // since the width is always unscaled it corresponds to the _original_ + // _unscaled_ font height to be used, we use that to calculate the + // bar height. Thus it is independent of the arguments height. + // ( see display of sqrt QQQ versus sqrt stack{Q#Q#Q#Q} ) + tools::Long nBarHeight = pNode->GetWidth( ) * 7 / 100; + tools::Long nBarWidth = pNode->GetBodyWidth( ) + pNode->GetBorderWidth( ); + Point aBarOffset( pNode->GetWidth( ), +pNode->GetBorderWidth( ) ); + Point aBarPos( maPosition + aBarOffset ); + + tools::Rectangle aBar( aBarPos, Size( nBarWidth, nBarHeight ) ); + + if (mrFormat.IsRightToLeft() && mrDev.GetOutDevType() != OUTDEV_WINDOW) + mrDev.ReMirror(aBar); + + mrDev.DrawRect( aBar ); +} + +void SmDrawingVisitor::Visit( SmPolyLineNode* pNode ) +{ + if ( pNode->IsPhantom( ) ) + return; + + tools::Long nBorderwidth = pNode->GetFont( ).GetBorderWidth( ); + + LineInfo aInfo; + aInfo.SetWidth( pNode->GetWidth( ) - 2 * nBorderwidth ); + + Point aOffset ( Point( ) - pNode->GetPolygon( ).GetBoundRect( ).TopLeft( ) + + Point( nBorderwidth, nBorderwidth ) ), + aPos ( maPosition + aOffset ); + + if (mrFormat.IsRightToLeft() && mrDev.GetOutDevType() != OUTDEV_WINDOW) + mrDev.ReMirror(aPos); + + pNode->GetPolygon( ).Move( aPos.X( ), aPos.Y( ) ); //Works because Polygon wraps a pointer + + SmTmpDevice aTmpDev ( mrDev, false ); + aTmpDev.SetLineColor( pNode->GetFont( ).GetColor( ) ); + + mrDev.DrawPolyLine( pNode->GetPolygon( ), aInfo ); +} + +void SmDrawingVisitor::Visit( SmRectangleNode* pNode ) +{ + if ( pNode->IsPhantom( ) ) + return; + + SmTmpDevice aTmpDev ( mrDev, false ); + aTmpDev.SetFillColor( pNode->GetFont( ).GetColor( ) ); + mrDev.SetLineColor( ); + aTmpDev.SetFont( pNode->GetFont( ) ); + + sal_uLong nTmpBorderWidth = pNode->GetFont( ).GetBorderWidth( ); + + // get rectangle and remove borderspace + tools::Rectangle aTmp ( pNode->AsRectangle( ) + maPosition - pNode->GetTopLeft( ) ); + aTmp.AdjustLeft(nTmpBorderWidth ); + aTmp.AdjustRight( -sal_Int32(nTmpBorderWidth) ); + aTmp.AdjustTop(nTmpBorderWidth ); + aTmp.AdjustBottom( -sal_Int32(nTmpBorderWidth) ); + + SAL_WARN_IF( aTmp.IsEmpty(), "starmath", "Empty rectangle" ); + + if (mrFormat.IsRightToLeft() && mrDev.GetOutDevType() != OUTDEV_WINDOW) + mrDev.ReMirror(aTmp); + + mrDev.DrawRect( aTmp ); +} + +void SmDrawingVisitor::DrawTextNode( SmTextNode* pNode ) +{ + if ( pNode->IsPhantom() || pNode->GetText().isEmpty() || pNode->GetText()[0] == '\0' ) + return; + + SmTmpDevice aTmpDev ( mrDev, false ); + aTmpDev.SetFont( pNode->GetFont( ) ); + + Point aPos ( maPosition ); + aPos.AdjustY(pNode->GetBaselineOffset( ) ); + + if (mrFormat.IsRightToLeft() && mrDev.GetOutDevType() != OUTDEV_WINDOW) + mrDev.ReMirror(aPos); + + mrDev.DrawStretchText( aPos, pNode->GetWidth( ), pNode->GetText( ) ); +} + +void SmDrawingVisitor::DrawSpecialNode( SmSpecialNode* pNode ) +{ + //! since this chars might come from any font, that we may not have + //! set to ALIGN_BASELINE yet, we do it now. + pNode->GetFont( ).SetAlignment( ALIGN_BASELINE ); + + DrawTextNode( pNode ); +} + +void SmDrawingVisitor::DrawChildren( SmStructureNode* pNode ) +{ + if ( pNode->IsPhantom( ) ) + return; + + Point rPosition = maPosition; + + for( auto pChild : *pNode ) + { + if(!pChild) + continue; + Point aOffset ( pChild->GetTopLeft( ) - pNode->GetTopLeft( ) ); + maPosition = rPosition + aOffset; + pChild->Accept( this ); + } +} + +// SmSetSelectionVisitor + +SmSetSelectionVisitor::SmSetSelectionVisitor( SmCaretPos startPos, SmCaretPos endPos, SmNode* pTree) + : maStartPos(startPos) + , maEndPos(endPos) + , mbSelecting(false) +{ + //Assume that pTree is a SmTableNode + SAL_WARN_IF(pTree->GetType() != SmNodeType::Table, "starmath", "pTree should be a SmTableNode!"); + //Visit root node, this is special as this node cannot be selected, but its children can! + if(pTree->GetType() == SmNodeType::Table){ + //Change state if maStartPos is in front of this node + if( maStartPos.pSelectedNode == pTree && maStartPos.nIndex == 0 ) + mbSelecting = !mbSelecting; + //Change state if maEndPos is in front of this node + if( maEndPos.pSelectedNode == pTree && maEndPos.nIndex == 0 ) + mbSelecting = !mbSelecting; + SAL_WARN_IF(mbSelecting, "starmath", "Caret positions needed to set mbSelecting about, shouldn't be possible!"); + + //Visit lines + for( auto pChild : *static_cast<SmStructureNode*>(pTree) ) + { + if(!pChild) + continue; + pChild->Accept( this ); + //If we started a selection in this line and it haven't ended, we do that now! + if(mbSelecting) { + mbSelecting = false; + SetSelectedOnAll(pChild); + //Set maStartPos and maEndPos to invalid positions, this ensures that an unused + //start or end (because we forced end above), doesn't start a new selection. + maStartPos = maEndPos = SmCaretPos(); + } + } + //Check if pTree isn't selected + SAL_WARN_IF(pTree->IsSelected(), "starmath", "pTree should never be selected!"); + //Discard the selection if there's a bug (it's better than crashing) + if(pTree->IsSelected()) + SetSelectedOnAll(pTree, false); + }else //This shouldn't happen, but I don't see any reason to die if it does + pTree->Accept(this); +} + +void SmSetSelectionVisitor::SetSelectedOnAll( SmNode* pSubTree, bool IsSelected ) { + pSubTree->SetSelected( IsSelected ); + + if(pSubTree->GetNumSubNodes() == 0) + return; + //Quick BFS to set all selections + for( auto pChild : *static_cast<SmStructureNode*>(pSubTree) ) + { + if(!pChild) + continue; + SetSelectedOnAll( pChild, IsSelected ); + } +} + +void SmSetSelectionVisitor::DefaultVisit( SmNode* pNode ) { + //Change state if maStartPos is in front of this node + if( maStartPos.pSelectedNode == pNode && maStartPos.nIndex == 0 ) + mbSelecting = !mbSelecting; + //Change state if maEndPos is in front of this node + if( maEndPos.pSelectedNode == pNode && maEndPos.nIndex == 0 ) + mbSelecting = !mbSelecting; + + //Cache current state + bool WasSelecting = mbSelecting; + bool ChangedState = false; + + //Set selected + pNode->SetSelected( mbSelecting ); + + //Visit children + if(pNode->GetNumSubNodes() > 0) + { + for( auto pChild : *static_cast<SmStructureNode*>(pNode) ) + { + if(!pChild) + continue; + pChild->Accept( this ); + ChangedState = ( WasSelecting != mbSelecting ) || ChangedState; + } + } + + //If state changed + if( ChangedState ) + { + //Select this node and all of its children + //(Make exception for SmBracebodyNode) + if( pNode->GetType() != SmNodeType::Bracebody || + !pNode->GetParent() || + pNode->GetParent()->GetType() != SmNodeType::Brace ) + SetSelectedOnAll( pNode ); + else + SetSelectedOnAll( pNode->GetParent() ); + /* If the equation is: sqrt{2 + 4} + 5 + * And the selection is: sqrt{2 + [4} +] 5 + * Where [ denotes maStartPos and ] denotes maEndPos + * Then the sqrt node should be selected, so that the + * effective selection is: [sqrt{2 + 4} +] 5 + * The same is the case if we swap maStartPos and maEndPos. + */ + } + + //Change state if maStartPos is after this node + if( maStartPos.pSelectedNode == pNode && maStartPos.nIndex == 1 ) + { + mbSelecting = !mbSelecting; + } + //Change state if maEndPos is after of this node + if( maEndPos.pSelectedNode == pNode && maEndPos.nIndex == 1 ) + { + mbSelecting = !mbSelecting; + } +} + +void SmSetSelectionVisitor::VisitCompositionNode( SmStructureNode* pNode ) +{ + //Change state if maStartPos is in front of this node + if( maStartPos.pSelectedNode == pNode && maStartPos.nIndex == 0 ) + mbSelecting = !mbSelecting; + //Change state if maEndPos is in front of this node + if( maEndPos.pSelectedNode == pNode && maEndPos.nIndex == 0 ) + mbSelecting = !mbSelecting; + + //Cache current state + bool WasSelecting = mbSelecting; + + //Visit children + for( auto pChild : *pNode ) + { + if(!pChild) + continue; + pChild->Accept( this ); + } + + //Set selected, if everything was selected + pNode->SetSelected( WasSelecting && mbSelecting ); + + //Change state if maStartPos is after this node + if( maStartPos.pSelectedNode == pNode && maStartPos.nIndex == 1 ) + mbSelecting = !mbSelecting; + //Change state if maEndPos is after of this node + if( maEndPos.pSelectedNode == pNode && maEndPos.nIndex == 1 ) + mbSelecting = !mbSelecting; +} + +void SmSetSelectionVisitor::Visit( SmTextNode* pNode ) { + tools::Long i1 = -1, + i2 = -1; + if( maStartPos.pSelectedNode == pNode ) + i1 = maStartPos.nIndex; + if( maEndPos.pSelectedNode == pNode ) + i2 = maEndPos.nIndex; + + tools::Long start, end; + pNode->SetSelected(true); + if( i1 != -1 && i2 != -1 ) { + start = std::min(i1, i2); + end = std::max(i1, i2); + } else if( mbSelecting && i1 != -1 ) { + start = 0; + end = i1; + mbSelecting = false; + } else if( mbSelecting && i2 != -1 ) { + start = 0; + end = i2; + mbSelecting = false; + } else if( !mbSelecting && i1 != -1 ) { + start = i1; + end = pNode->GetText().getLength(); + mbSelecting = true; + } else if( !mbSelecting && i2 != -1 ) { + start = i2; + end = pNode->GetText().getLength(); + mbSelecting = true; + } else if( mbSelecting ) { + start = 0; + end = pNode->GetText().getLength(); + } else { + pNode->SetSelected( false ); + start = 0; + end = 0; + } + pNode->SetSelected( start != end ); + pNode->SetSelectionStart( start ); + pNode->SetSelectionEnd( end ); +} + +void SmSetSelectionVisitor::Visit( SmExpressionNode* pNode ) { + VisitCompositionNode( pNode ); +} + +void SmSetSelectionVisitor::Visit( SmLineNode* pNode ) { + VisitCompositionNode( pNode ); +} + +void SmSetSelectionVisitor::Visit( SmAlignNode* pNode ) { + VisitCompositionNode( pNode ); +} + +void SmSetSelectionVisitor::Visit( SmBinHorNode* pNode ) { + VisitCompositionNode( pNode ); +} + +void SmSetSelectionVisitor::Visit( SmUnHorNode* pNode ) { + VisitCompositionNode( pNode ); +} + +void SmSetSelectionVisitor::Visit( SmFontNode* pNode ) { + VisitCompositionNode( pNode ); +} + +// SmCaretPosGraphBuildingVisitor + +SmCaretPosGraphBuildingVisitor::SmCaretPosGraphBuildingVisitor( SmNode* pRootNode ) + : mpRightMost(nullptr) + , mpGraph(new SmCaretPosGraph) +{ + //pRootNode should always be a table + SAL_WARN_IF( pRootNode->GetType( ) != SmNodeType::Table, "starmath", "pRootNode must be a table node"); + //Handle the special case where SmNodeType::Table is used a rootnode + if( pRootNode->GetType( ) == SmNodeType::Table ){ + //Children are SmLineNodes + //Or so I thought... Apparently, the children can be instances of SmExpression + //especially if there's an error in the formula... So here we go, a simple work around. + for( auto pChild : *static_cast<SmStructureNode*>(pRootNode) ) + { + if(!pChild) + continue; + mpRightMost = mpGraph->Add( SmCaretPos( pChild, 0 ) ); + pChild->Accept( this ); + } + }else + pRootNode->Accept(this); +} + +SmCaretPosGraphBuildingVisitor::~SmCaretPosGraphBuildingVisitor() +{ +} + +void SmCaretPosGraphBuildingVisitor::Visit( SmLineNode* pNode ){ + for( auto pChild : *pNode ) + { + if(!pChild) + continue; + pChild->Accept( this ); + } +} + +/** Build SmCaretPosGraph for SmTableNode + * This method covers cases where SmTableNode is used in a binom or stack, + * the special case where it is used as root node for the entire formula is + * handled in the constructor. + */ +void SmCaretPosGraphBuildingVisitor::Visit( SmTableNode* pNode ){ + SmCaretPosGraphEntry *left = mpRightMost, + *right = mpGraph->Add( SmCaretPos( pNode, 1) ); + bool bIsFirst = true; + for( auto pChild : *pNode ) + { + if(!pChild) + continue; + mpRightMost = mpGraph->Add( SmCaretPos( pChild, 0 ), left); + if(bIsFirst) + left->SetRight(mpRightMost); + pChild->Accept( this ); + mpRightMost->SetRight(right); + if(bIsFirst) + right->SetLeft(mpRightMost); + bIsFirst = false; + } + mpRightMost = right; +} + +/** Build SmCaretPosGraph for SmSubSupNode + * + * The child positions in a SubSupNode, where H is the body: + * \code + * CSUP + * + * LSUP H H RSUP + * H H + * HHHH + * H H + * LSUB H H RSUB + * + * CSUB + * \endcode + * + * Graph over these, where "left" is before the SmSubSupNode and "right" is after: + * \dot + * digraph Graph{ + * left -> H; + * H -> right; + * LSUP -> H; + * LSUB -> H; + * CSUP -> right; + * CSUB -> right; + * RSUP -> right; + * RSUB -> right; + * }; + * \enddot + * + */ +void SmCaretPosGraphBuildingVisitor::Visit( SmSubSupNode* pNode ) +{ + SmCaretPosGraphEntry *left, + *right, + *bodyLeft, + *bodyRight; + + assert(mpRightMost); + left = mpRightMost; + + //Create bodyLeft + SAL_WARN_IF( !pNode->GetBody(), "starmath", "SmSubSupNode Doesn't have a body!" ); + bodyLeft = mpGraph->Add( SmCaretPos( pNode->GetBody( ), 0 ), left ); + left->SetRight( bodyLeft ); //TODO: Don't make this if LSUP or LSUB are NULL ( not sure??? ) + + //Create right + right = mpGraph->Add( SmCaretPos( pNode, 1 ) ); + + //Visit the body, to get bodyRight + mpRightMost = bodyLeft; + pNode->GetBody( )->Accept( this ); + bodyRight = mpRightMost; + bodyRight->SetRight( right ); + right->SetLeft( bodyRight ); + + SmNode* pChild; + for (SmSubSup const nodeType : { LSUP, LSUB, CSUP, CSUB, RSUP, RSUB }) + { + pChild = pNode->GetSubSup(nodeType); + if( pChild ) + { + SmCaretPosGraphEntry *cLeft; //Child left + cLeft = mpGraph->Add( SmCaretPos( pChild, 0 ), ((nodeType == RSUP) || (nodeType == RSUB))?bodyRight:left ); + + mpRightMost = cLeft; + pChild->Accept( this ); + + mpRightMost->SetRight( ((nodeType == LSUP) || (nodeType == LSUB))?bodyLeft:right ); + } + } + + //Set return parameters + mpRightMost = right; +} + +/** Build caret position for SmOperNode + * + * If first child is an SmSubSupNode we will ignore its + * body, as this body is a SmMathSymbol, for SUM, INT or similar + * that shouldn't be subject to modification. + * If first child is not a SmSubSupNode, ignore it completely + * as it is a SmMathSymbol. + * + * The child positions in a SmOperNode, where H is symbol, e.g. int, sum or similar: + * \code + * TO + * + * LSUP H H RSUP BBB BB BBB B B + * H H B B B B B B B B + * HHHH BBB B B B B B + * H H B B B B B B B + * LSUB H H RSUB BBB BB BBB B + * + * FROM + * \endcode + * Notice, CSUP, etc. are actually grandchildren, but inorder to ignore H, these are visited + * from here. If they are present, that is if pOper is an instance of SmSubSupNode. + * + * Graph over these, where "left" is before the SmOperNode and "right" is after: + * \dot + * digraph Graph{ + * left -> BODY; + * BODY -> right; + * LSUP -> BODY; + * LSUB -> BODY; + * TO -> BODY; + * FROM -> BODY; + * RSUP -> BODY; + * RSUB -> BODY; + * }; + * \enddot + */ +void SmCaretPosGraphBuildingVisitor::Visit( SmOperNode* pNode ) +{ + SmNode *pOper = pNode->GetSubNode( 0 ), + *pBody = pNode->GetSubNode( 1 ); + + SmCaretPosGraphEntry *left = mpRightMost, + *bodyLeft, + *bodyRight, + *right; + //Create body left + bodyLeft = mpGraph->Add( SmCaretPos( pBody, 0 ), left ); + left->SetRight( bodyLeft ); + + //Visit body, get bodyRight + mpRightMost = bodyLeft; + pBody->Accept( this ); + bodyRight = mpRightMost; + + //Create right + right = mpGraph->Add( SmCaretPos( pNode, 1 ), bodyRight ); + bodyRight->SetRight( right ); + + //Get subsup pNode if any + SmSubSupNode* pSubSup = pOper->GetType( ) == SmNodeType::SubSup ? static_cast<SmSubSupNode*>(pOper) : nullptr; + + if( pSubSup ) { + SmNode* pChild; + for (SmSubSup const nodeType : { LSUP, LSUB, CSUP, CSUB, RSUP, RSUB }) + { + pChild = pSubSup->GetSubSup(nodeType); + if( pChild ) + { + //Create position in front of pChild + SmCaretPosGraphEntry *childLeft = mpGraph->Add( SmCaretPos( pChild, 0 ), left ); + //Visit pChild + mpRightMost = childLeft; + pChild->Accept( this ); + //Set right on mpRightMost from pChild + mpRightMost->SetRight( bodyLeft ); + } + } + } + + //Return right + mpRightMost = right; +} + +void SmCaretPosGraphBuildingVisitor::Visit( SmMatrixNode* pNode ) +{ + SmCaretPosGraphEntry *left = mpRightMost, + *right = mpGraph->Add( SmCaretPos( pNode, 1 ) ); + + for (size_t i = 0; i < pNode->GetNumRows(); ++i) + { + SmCaretPosGraphEntry* r = left; + for (size_t j = 0; j < pNode->GetNumCols(); ++j) + { + SmNode* pSubNode = pNode->GetSubNode( i * pNode->GetNumCols( ) + j ); + + mpRightMost = mpGraph->Add( SmCaretPos( pSubNode, 0 ), r ); + if( j != 0 || ( pNode->GetNumRows() - 1U ) / 2 == i ) + r->SetRight( mpRightMost ); + + pSubNode->Accept( this ); + + r = mpRightMost; + } + mpRightMost->SetRight( right ); + if( ( pNode->GetNumRows() - 1U ) / 2 == i ) + right->SetLeft( mpRightMost ); + } + + mpRightMost = right; +} + +/** Build SmCaretPosGraph for SmTextNode + * + * Lines in an SmTextNode: + * \code + * A B C + * \endcode + * Where A B and C are characters in the text. + * + * Graph over these, where "left" is before the SmTextNode and "right" is after: + * \dot + * digraph Graph{ + * left -> A; + * A -> B + * B -> right; + * }; + * \enddot + * Notice that C and right is the same position here. + */ +void SmCaretPosGraphBuildingVisitor::Visit( SmTextNode* pNode ) +{ + SAL_WARN_IF( pNode->GetText().isEmpty(), "starmath", "Empty SmTextNode is bad" ); + + OUString& aText = pNode->GetText(); + sal_Int32 i = 0; + while (i < aText.getLength()) + { + aText.iterateCodePoints(&i); + SmCaretPosGraphEntry* pRight = mpRightMost; + mpRightMost = mpGraph->Add( SmCaretPos( pNode, i ), pRight ); + pRight->SetRight( mpRightMost ); + } +} + +/** Build SmCaretPosGraph for SmBinVerNode + * + * Lines in an SmBinVerNode: + * \code + * A + * ----- + * B + * \endcode + * + * Graph over these, where "left" is before the SmBinVerNode and "right" is after: + * \dot + * digraph Graph{ + * left -> A; + * A -> right; + * B -> right; + * }; + * \enddot + */ +void SmCaretPosGraphBuildingVisitor::Visit( SmBinVerNode* pNode ) +{ + //None if these children can be NULL, see SmBinVerNode::Arrange + SmNode *pNum = pNode->GetSubNode( 0 ), + *pDenom = pNode->GetSubNode( 2 ); + + SmCaretPosGraphEntry *left, + *right, + *numLeft, + *denomLeft; + + assert(mpRightMost); + //Set left + left = mpRightMost; + + //Create right + right = mpGraph->Add( SmCaretPos( pNode, 1 ) ); + + //Create numLeft + numLeft = mpGraph->Add( SmCaretPos( pNum, 0 ), left ); + left->SetRight( numLeft ); + + //Visit pNum + mpRightMost = numLeft; + pNum->Accept( this ); + mpRightMost->SetRight( right ); + right->SetLeft( mpRightMost ); + + //Create denomLeft + denomLeft = mpGraph->Add( SmCaretPos( pDenom, 0 ), left ); + + //Visit pDenom + mpRightMost = denomLeft; + pDenom->Accept( this ); + mpRightMost->SetRight( right ); + + //Set return parameter + mpRightMost = right; +} + +/** Build SmCaretPosGraph for SmVerticalBraceNode + * + * Lines in an SmVerticalBraceNode: + * \code + * pScript + * ________ + * / \ + * pBody + * \endcode + * + */ +void SmCaretPosGraphBuildingVisitor::Visit( SmVerticalBraceNode* pNode ) +{ + SmNode *pBody = pNode->Body(), + *pScript = pNode->Script(); + //None of these children can be NULL + + SmCaretPosGraphEntry *left, + *bodyLeft, + *scriptLeft, + *right; + + left = mpRightMost; + + //Create right + right = mpGraph->Add( SmCaretPos( pNode, 1 ) ); + + //Create bodyLeft + bodyLeft = mpGraph->Add( SmCaretPos( pBody, 0 ), left ); + left->SetRight( bodyLeft ); + mpRightMost = bodyLeft; + pBody->Accept( this ); + mpRightMost->SetRight( right ); + right->SetLeft( mpRightMost ); + + //Create script + scriptLeft = mpGraph->Add( SmCaretPos( pScript, 0 ), left ); + mpRightMost = scriptLeft; + pScript->Accept( this ); + mpRightMost->SetRight( right ); + + //Set return value + mpRightMost = right; +} + +/** Build SmCaretPosGraph for SmBinDiagonalNode + * + * Lines in an SmBinDiagonalNode: + * \code + * A / + * / + * / B + * \endcode + * Where A and B are lines. + * + * Used in formulas such as "A wideslash B" + */ +void SmCaretPosGraphBuildingVisitor::Visit( SmBinDiagonalNode* pNode ) +{ + SmNode *A = pNode->GetSubNode( 0 ), + *B = pNode->GetSubNode( 1 ); + + SmCaretPosGraphEntry *left, + *leftA, + *rightA, + *leftB, + *right; + left = mpRightMost; + + //Create right + right = mpGraph->Add( SmCaretPos( pNode, 1 ) ); + + //Create left A + leftA = mpGraph->Add( SmCaretPos( A, 0 ), left ); + left->SetRight( leftA ); + + //Visit A + mpRightMost = leftA; + A->Accept( this ); + rightA = mpRightMost; + + //Create left B + leftB = mpGraph->Add( SmCaretPos( B, 0 ), rightA ); + rightA->SetRight( leftB ); + + //Visit B + mpRightMost = leftB; + B->Accept( this ); + mpRightMost->SetRight( right ); + right->SetLeft( mpRightMost ); + + //Set return value + mpRightMost = right; +} + +//Straight forward ( I think ) +void SmCaretPosGraphBuildingVisitor::Visit( SmBinHorNode* pNode ) +{ + for( auto pChild : *pNode ) + { + if(!pChild) + continue; + pChild->Accept( this ); + } +} +void SmCaretPosGraphBuildingVisitor::Visit( SmUnHorNode* pNode ) +{ + // Unary operator node + for( auto pChild : *pNode ) + { + if(!pChild) + continue; + pChild->Accept( this ); + } +} + +void SmCaretPosGraphBuildingVisitor::Visit( SmExpressionNode* pNode ) +{ + for( auto pChild : *pNode ) + { + if(!pChild) + continue; + pChild->Accept( this ); + } +} + +void SmCaretPosGraphBuildingVisitor::Visit( SmFontNode* pNode ) +{ + //Has only got one child, should act as an expression if possible + for( auto pChild : *pNode ) + { + if(!pChild) + continue; + pChild->Accept( this ); + } +} + +/** Build SmCaretPosGraph for SmBracebodyNode + * Acts as an SmExpressionNode + * + * Below is an example of a formula tree that has multiple children for SmBracebodyNode + * \dot + * digraph { + * labelloc = "t"; + * label= "Equation: \"lbrace i mline i in setZ rbrace\""; + * n0 [label="SmTableNode"]; + * n0 -> n1 [label="0"]; + * n1 [label="SmLineNode"]; + * n1 -> n2 [label="0"]; + * n2 [label="SmExpressionNode"]; + * n2 -> n3 [label="0"]; + * n3 [label="SmBraceNode"]; + * n3 -> n4 [label="0"]; + * n4 [label="SmMathSymbolNode: {"]; + * n3 -> n5 [label="1"]; + * n5 [label="SmBracebodyNode"]; + * n5 -> n6 [label="0"]; + * n6 [label="SmExpressionNode"]; + * n6 -> n7 [label="0"]; + * n7 [label="SmTextNode: i"]; + * n5 -> n8 [label="1"]; + * n8 [label="SmMathSymbolNode: |"]; // Unicode "VERTICAL LINE" + * n5 -> n9 [label="2"]; + * n9 [label="SmExpressionNode"]; + * n9 -> n10 [label="0"]; + * n10 [label="SmBinHorNode"]; + * n10 -> n11 [label="0"]; + * n11 [label="SmTextNode: i"]; + * n10 -> n12 [label="1"]; + * n12 [label="SmMathSymbolNode: ∈"]; // Unicode "ELEMENT OF" + * n10 -> n13 [label="2"]; + * n13 [label="SmMathSymbolNode: ℤ"]; // Unicode "DOUBLE-STRUCK CAPITAL Z" + * n3 -> n14 [label="2"]; + * n14 [label="SmMathSymbolNode: }"]; + * } + * \enddot + */ +void SmCaretPosGraphBuildingVisitor::Visit( SmBracebodyNode* pNode ) +{ + for( auto pChild : *pNode ) + { + if(!pChild) + continue; + SmCaretPosGraphEntry* pStart = mpGraph->Add( SmCaretPos( pChild, 0), mpRightMost ); + mpRightMost->SetRight( pStart ); + mpRightMost = pStart; + pChild->Accept( this ); + } +} + +/** Build SmCaretPosGraph for SmAlignNode + * Acts as an SmExpressionNode, as it only has one child this okay + */ +void SmCaretPosGraphBuildingVisitor::Visit( SmAlignNode* pNode ) +{ + for( auto pChild : *pNode ) + { + if(!pChild) + continue; + pChild->Accept( this ); + } +} + +/** Build SmCaretPosGraph for SmRootNode + * + * Lines in an SmRootNode: + * \code + * _________ + * A/ + * \/ B + * + * \endcode + * A: pExtra ( optional, can be NULL ), + * B: pBody + * + * Graph over these, where "left" is before the SmRootNode and "right" is after: + * \dot + * digraph Graph{ + * left -> B; + * B -> right; + * A -> B; + * } + * \enddot + */ +void SmCaretPosGraphBuildingVisitor::Visit( SmRootNode* pNode ) +{ + SmNode *pExtra = pNode->GetSubNode( 0 ), //Argument, NULL for sqrt, and SmTextNode if cubicroot + *pBody = pNode->GetSubNode( 2 ); //Body of the root + assert(pBody); + + SmCaretPosGraphEntry *left, + *right, + *bodyLeft, + *bodyRight; + + //Get left and save it + assert(mpRightMost); + left = mpRightMost; + + //Create body left + bodyLeft = mpGraph->Add( SmCaretPos( pBody, 0 ), left ); + left->SetRight( bodyLeft ); + + //Create right + right = mpGraph->Add( SmCaretPos( pNode, 1 ) ); + + //Visit body + mpRightMost = bodyLeft; + pBody->Accept( this ); + bodyRight = mpRightMost; + bodyRight->SetRight( right ); + right->SetLeft( bodyRight ); + + //Visit pExtra + if( pExtra ){ + mpRightMost = mpGraph->Add( SmCaretPos( pExtra, 0 ), left ); + pExtra->Accept( this ); + mpRightMost->SetRight( bodyLeft ); + } + + mpRightMost = right; +} + + +/** Build SmCaretPosGraph for SmPlaceNode + * Consider this a single character. + */ +void SmCaretPosGraphBuildingVisitor::Visit( SmPlaceNode* pNode ) +{ + SmCaretPosGraphEntry* right = mpGraph->Add( SmCaretPos( pNode, 1 ), mpRightMost ); + mpRightMost->SetRight( right ); + mpRightMost = right; +} + +/** SmErrorNode is context dependent metadata, it can't be selected + * + * @remarks There's no point in deleting, copying and/or moving an instance + * of SmErrorNode as it may not exist in another context! Thus there are no + * positions to select an SmErrorNode. + */ +void SmCaretPosGraphBuildingVisitor::Visit( SmErrorNode* ) +{ +} + +/** Build SmCaretPosGraph for SmBlankNode + * Consider this a single character, as it is only a blank space + */ +void SmCaretPosGraphBuildingVisitor::Visit( SmBlankNode* pNode ) +{ + SmCaretPosGraphEntry* right = mpGraph->Add( SmCaretPos( pNode, 1 ), mpRightMost ); + mpRightMost->SetRight( right ); + mpRightMost = right; +} + +/** Build SmCaretPosGraph for SmBraceNode + * + * Lines in an SmBraceNode: + * \code + * | | + * | B | + * | | + * \endcode + * B: Body + * + * Graph over these, where "left" is before the SmBraceNode and "right" is after: + * \dot + * digraph Graph{ + * left -> B; + * B -> right; + * } + * \enddot + */ +void SmCaretPosGraphBuildingVisitor::Visit( SmBraceNode* pNode ) +{ + SmNode* pBody = pNode->Body(); + + SmCaretPosGraphEntry *left = mpRightMost, + *right = mpGraph->Add( SmCaretPos( pNode, 1 ) ); + + if( pBody->GetType() != SmNodeType::Bracebody ) { + mpRightMost = mpGraph->Add( SmCaretPos( pBody, 0 ), left ); + left->SetRight( mpRightMost ); + }else + mpRightMost = left; + + pBody->Accept( this ); + mpRightMost->SetRight( right ); + right->SetLeft( mpRightMost ); + + mpRightMost = right; +} + +/** Build SmCaretPosGraph for SmAttributeNode + * + * Lines in an SmAttributeNode: + * \code + * Attr + * Body + * \endcode + * + * There's a body and an attribute, the construction is used for "widehat A", where "A" is the body + * and "^" is the attribute ( note GetScaleMode( ) on SmAttributeNode tells how the attribute should be + * scaled ). + */ +void SmCaretPosGraphBuildingVisitor::Visit( SmAttributeNode* pNode ) +{ + SmNode *pAttr = pNode->Attribute(), + *pBody = pNode->Body(); + assert(pAttr); + assert(pBody); + + SmCaretPosGraphEntry *left = mpRightMost, + *attrLeft, + *bodyLeft, + *bodyRight, + *right; + + //Creating bodyleft + bodyLeft = mpGraph->Add( SmCaretPos( pBody, 0 ), left ); + left->SetRight( bodyLeft ); + + //Creating right + right = mpGraph->Add( SmCaretPos( pNode, 1 ) ); + + //Visit the body + mpRightMost = bodyLeft; + pBody->Accept( this ); + bodyRight = mpRightMost; + bodyRight->SetRight( right ); + right->SetLeft( bodyRight ); + + //Create attrLeft + attrLeft = mpGraph->Add( SmCaretPos( pAttr, 0 ), left ); + + //Visit attribute + mpRightMost = attrLeft; + pAttr->Accept( this ); + mpRightMost->SetRight( right ); + + //Set return value + mpRightMost = right; +} + +//Consider these single symbols +void SmCaretPosGraphBuildingVisitor::Visit( SmSpecialNode* pNode ) +{ + SmCaretPosGraphEntry* right = mpGraph->Add( SmCaretPos( pNode, 1 ), mpRightMost ); + mpRightMost->SetRight( right ); + mpRightMost = right; +} +void SmCaretPosGraphBuildingVisitor::Visit( SmGlyphSpecialNode* pNode ) +{ + SmCaretPosGraphEntry* right = mpGraph->Add( SmCaretPos( pNode, 1 ), mpRightMost ); + mpRightMost->SetRight( right ); + mpRightMost = right; +} +void SmCaretPosGraphBuildingVisitor::Visit( SmMathSymbolNode* pNode ) +{ + SmCaretPosGraphEntry* right = mpGraph->Add( SmCaretPos( pNode, 1 ), mpRightMost ); + mpRightMost->SetRight( right ); + mpRightMost = right; +} + +void SmCaretPosGraphBuildingVisitor::Visit( SmRootSymbolNode* ) +{ + //Do nothing +} + +void SmCaretPosGraphBuildingVisitor::Visit( SmRectangleNode* ) +{ + //Do nothing +} +void SmCaretPosGraphBuildingVisitor::Visit( SmPolyLineNode* ) +{ + //Do nothing +} + +// SmCloningVisitor + +SmNode* SmCloningVisitor::Clone( SmNode* pNode ) +{ + SmNode* pCurrResult = mpResult; + pNode->Accept( this ); + SmNode* pClone = mpResult; + mpResult = pCurrResult; + return pClone; +} + +void SmCloningVisitor::CloneNodeAttr( SmNode const * pSource, SmNode* pTarget ) +{ + pTarget->SetScaleMode( pSource->GetScaleMode( ) ); + //Other attributes are set when prepare or arrange is executed + //and may depend on stuff not being cloned here. +} + +void SmCloningVisitor::CloneKids( SmStructureNode* pSource, SmStructureNode* pTarget ) +{ + //Cache current result + SmNode* pCurrResult = mpResult; + + //Create array for holding clones + size_t nSize = pSource->GetNumSubNodes( ); + SmNodeArray aNodes( nSize ); + + //Clone children + for (size_t i = 0; i < nSize; ++i) + { + SmNode* pKid; + if( nullptr != ( pKid = pSource->GetSubNode( i ) ) ) + pKid->Accept( this ); + else + mpResult = nullptr; + aNodes[i] = mpResult; + } + + //Set subnodes of pTarget + pTarget->SetSubNodes( std::move(aNodes) ); + + //Restore result as where prior to call + mpResult = pCurrResult; +} + +void SmCloningVisitor::Visit( SmTableNode* pNode ) +{ + SmTableNode* pClone = new SmTableNode( pNode->GetToken( ) ); + pClone->SetSelection( pNode->GetSelection() ); + CloneNodeAttr( pNode, pClone ); + CloneKids( pNode, pClone ); + mpResult = pClone; +} + +void SmCloningVisitor::Visit( SmBraceNode* pNode ) +{ + SmBraceNode* pClone = new SmBraceNode( pNode->GetToken( ) ); + pClone->SetSelection( pNode->GetSelection() ); + CloneNodeAttr( pNode, pClone ); + CloneKids( pNode, pClone ); + mpResult = pClone; +} + +void SmCloningVisitor::Visit( SmBracebodyNode* pNode ) +{ + SmBracebodyNode* pClone = new SmBracebodyNode( pNode->GetToken( ) ); + pClone->SetSelection( pNode->GetSelection() ); + CloneNodeAttr( pNode, pClone ); + CloneKids( pNode, pClone ); + mpResult = pClone; +} + +void SmCloningVisitor::Visit( SmOperNode* pNode ) +{ + SmOperNode* pClone = new SmOperNode( pNode->GetToken( ) ); + pClone->SetSelection( pNode->GetSelection() ); + CloneNodeAttr( pNode, pClone ); + CloneKids( pNode, pClone ); + mpResult = pClone; +} + +void SmCloningVisitor::Visit( SmAlignNode* pNode ) +{ + SmAlignNode* pClone = new SmAlignNode( pNode->GetToken( ) ); + pClone->SetSelection( pNode->GetSelection() ); + CloneNodeAttr( pNode, pClone ); + CloneKids( pNode, pClone ); + mpResult = pClone; +} + +void SmCloningVisitor::Visit( SmAttributeNode* pNode ) +{ + SmAttributeNode* pClone = new SmAttributeNode( pNode->GetToken( ) ); + pClone->SetSelection( pNode->GetSelection() ); + CloneNodeAttr( pNode, pClone ); + CloneKids( pNode, pClone ); + mpResult = pClone; +} + +void SmCloningVisitor::Visit( SmFontNode* pNode ) +{ + SmFontNode* pClone = new SmFontNode( pNode->GetToken( ) ); + pClone->SetSelection( pNode->GetSelection() ); + pClone->SetSizeParameter( pNode->GetSizeParameter( ), pNode->GetSizeType( ) ); + CloneNodeAttr( pNode, pClone ); + CloneKids( pNode, pClone ); + mpResult = pClone; +} + +void SmCloningVisitor::Visit( SmUnHorNode* pNode ) +{ + SmUnHorNode* pClone = new SmUnHorNode( pNode->GetToken( ) ); + pClone->SetSelection( pNode->GetSelection() ); + CloneNodeAttr( pNode, pClone ); + CloneKids( pNode, pClone ); + mpResult = pClone; +} + +void SmCloningVisitor::Visit( SmBinHorNode* pNode ) +{ + SmBinHorNode* pClone = new SmBinHorNode( pNode->GetToken( ) ); + pClone->SetSelection( pNode->GetSelection() ); + CloneNodeAttr( pNode, pClone ); + CloneKids( pNode, pClone ); + mpResult = pClone; +} + +void SmCloningVisitor::Visit( SmBinVerNode* pNode ) +{ + SmBinVerNode* pClone = new SmBinVerNode( pNode->GetToken( ) ); + pClone->SetSelection( pNode->GetSelection() ); + CloneNodeAttr( pNode, pClone ); + CloneKids( pNode, pClone ); + mpResult = pClone; +} + +void SmCloningVisitor::Visit( SmBinDiagonalNode* pNode ) +{ + SmBinDiagonalNode *pClone = new SmBinDiagonalNode( pNode->GetToken( ) ); + pClone->SetSelection( pNode->GetSelection() ); + pClone->SetAscending( pNode->IsAscending( ) ); + CloneNodeAttr( pNode, pClone ); + CloneKids( pNode, pClone ); + mpResult = pClone; +} + +void SmCloningVisitor::Visit( SmSubSupNode* pNode ) +{ + SmSubSupNode *pClone = new SmSubSupNode( pNode->GetToken( ) ); + pClone->SetSelection( pNode->GetSelection() ); + pClone->SetUseLimits( pNode->IsUseLimits( ) ); + CloneNodeAttr( pNode, pClone ); + CloneKids( pNode, pClone ); + mpResult = pClone; +} + +void SmCloningVisitor::Visit( SmMatrixNode* pNode ) +{ + SmMatrixNode *pClone = new SmMatrixNode( pNode->GetToken( ) ); + pClone->SetSelection( pNode->GetSelection() ); + pClone->SetRowCol( pNode->GetNumRows( ), pNode->GetNumCols( ) ); + CloneNodeAttr( pNode, pClone ); + CloneKids( pNode, pClone ); + mpResult = pClone; +} + +void SmCloningVisitor::Visit( SmPlaceNode* pNode ) +{ + mpResult = new SmPlaceNode( pNode->GetToken( ) ); + mpResult->SetSelection( pNode->GetSelection() ); + CloneNodeAttr( pNode, mpResult ); +} + +void SmCloningVisitor::Visit( SmTextNode* pNode ) +{ + SmTextNode* pClone = new SmTextNode( pNode->GetToken( ), pNode->GetFontDesc( ) ); + pClone->SetSelection( pNode->GetSelection() ); + pClone->ChangeText( pNode->GetText( ) ); + CloneNodeAttr( pNode, pClone ); + mpResult = pClone; +} + +void SmCloningVisitor::Visit( SmSpecialNode* pNode ) +{ + mpResult = new SmSpecialNode( pNode->GetToken( ) ); + mpResult->SetSelection( pNode->GetSelection() ); + CloneNodeAttr( pNode, mpResult ); +} + +void SmCloningVisitor::Visit( SmGlyphSpecialNode* pNode ) +{ + mpResult = new SmGlyphSpecialNode( pNode->GetToken( ) ); + mpResult->SetSelection( pNode->GetSelection() ); + CloneNodeAttr( pNode, mpResult ); +} + +void SmCloningVisitor::Visit( SmMathSymbolNode* pNode ) +{ + mpResult = new SmMathSymbolNode( pNode->GetToken( ) ); + mpResult->SetSelection( pNode->GetSelection() ); + CloneNodeAttr( pNode, mpResult ); +} + +void SmCloningVisitor::Visit( SmBlankNode* pNode ) +{ + SmBlankNode* pClone = new SmBlankNode( pNode->GetToken( ) ); + pClone->SetSelection( pNode->GetSelection() ); + pClone->SetBlankNum( pNode->GetBlankNum( ) ); + mpResult = pClone; + CloneNodeAttr( pNode, mpResult ); +} + +void SmCloningVisitor::Visit( SmErrorNode* pNode ) +{ + mpResult = new SmErrorNode( pNode->GetToken( ) ); + mpResult->SetSelection( pNode->GetSelection() ); + CloneNodeAttr( pNode, mpResult ); +} + +void SmCloningVisitor::Visit( SmLineNode* pNode ) +{ + SmLineNode* pClone = new SmLineNode( pNode->GetToken( ) ); + pClone->SetSelection( pNode->GetSelection() ); + CloneNodeAttr( pNode, pClone ); + CloneKids( pNode, pClone ); + mpResult = pClone; +} + +void SmCloningVisitor::Visit( SmExpressionNode* pNode ) +{ + SmExpressionNode* pClone = new SmExpressionNode( pNode->GetToken( ) ); + pClone->SetSelection( pNode->GetSelection() ); + CloneNodeAttr( pNode, pClone ); + CloneKids( pNode, pClone ); + mpResult = pClone; +} + +void SmCloningVisitor::Visit( SmPolyLineNode* pNode ) +{ + mpResult = new SmPolyLineNode( pNode->GetToken( ) ); + mpResult->SetSelection( pNode->GetSelection() ); + CloneNodeAttr( pNode, mpResult ); +} + +void SmCloningVisitor::Visit( SmRootNode* pNode ) +{ + SmRootNode* pClone = new SmRootNode( pNode->GetToken( ) ); + pClone->SetSelection( pNode->GetSelection() ); + CloneNodeAttr( pNode, pClone ); + CloneKids( pNode, pClone ); + mpResult = pClone; +} + +void SmCloningVisitor::Visit( SmRootSymbolNode* pNode ) +{ + mpResult = new SmRootSymbolNode( pNode->GetToken( ) ); + mpResult->SetSelection( pNode->GetSelection() ); + CloneNodeAttr( pNode, mpResult ); +} + +void SmCloningVisitor::Visit( SmRectangleNode* pNode ) +{ + mpResult = new SmRectangleNode( pNode->GetToken( ) ); + mpResult->SetSelection( pNode->GetSelection() ); + CloneNodeAttr( pNode, mpResult ); +} + +void SmCloningVisitor::Visit( SmVerticalBraceNode* pNode ) +{ + SmVerticalBraceNode* pClone = new SmVerticalBraceNode( pNode->GetToken( ) ); + pClone->SetSelection( pNode->GetSelection() ); + CloneNodeAttr( pNode, pClone ); + CloneKids( pNode, pClone ); + mpResult = pClone; +} + +// SmSelectionDrawingVisitor + +SmSelectionDrawingVisitor::SmSelectionDrawingVisitor( OutputDevice& rDevice, SmNode* pTree, const Point& rOffset ) + : SmSelectionRectanglesVisitor( rDevice, pTree ) +{ + //Draw selection if there's any + if(GetSelection().IsEmpty()) return; + + tools::Rectangle aSelectionArea = GetSelection() + rOffset; + + //Save device state + rDevice.Push( vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR ); + //Change colors + rDevice.SetLineColor( ); + rDevice.SetFillColor( COL_LIGHTGRAY ); + + //Draw rectangle + rDevice.DrawRect( aSelectionArea ); + + //Restore device state + rDevice.Pop( ); +} + +// SmSelectionRectanglesVisitor + +SmSelectionRectanglesVisitor::SmSelectionRectanglesVisitor(OutputDevice& rDevice, SmNode* pTree) + : mrDev(rDevice) +{ + // Visit everything + SAL_WARN_IF(!pTree, "starmath", "pTree can't be null!"); + if (pTree) + pTree->Accept(this); +} + +void SmSelectionRectanglesVisitor::DefaultVisit( SmNode* pNode ) +{ + if( pNode->IsSelected( ) ) + ExtendSelectionArea( pNode->AsRectangle( ) ); + VisitChildren( pNode ); +} + +void SmSelectionRectanglesVisitor::VisitChildren( SmNode* pNode ) +{ + if(pNode->GetNumSubNodes() == 0) + return; + for( auto pChild : *static_cast<SmStructureNode*>(pNode) ) + { + if(!pChild) + continue; + pChild->Accept( this ); + } +} + +void SmSelectionRectanglesVisitor::Visit( SmTextNode* pNode ) +{ + if( !pNode->IsSelected()) + return; + + mrDev.Push( vcl::PushFlags::TEXTCOLOR | vcl::PushFlags::FONT ); + + mrDev.SetFont( pNode->GetFont( ) ); + Point Position = pNode->GetTopLeft( ); + tools::Long left = Position.getX( ) + mrDev.GetTextWidth( pNode->GetText( ), 0, pNode->GetSelectionStart( ) ); + tools::Long right = Position.getX( ) + mrDev.GetTextWidth( pNode->GetText( ), 0, pNode->GetSelectionEnd( ) ); + tools::Long top = Position.getY( ); + tools::Long bottom = top + pNode->GetHeight( ); + tools::Rectangle rect( left, top, right, bottom ); + + ExtendSelectionArea( rect ); + + mrDev.Pop( ); +} + +// SmNodeToTextVisitor + +SmNodeToTextVisitor::SmNodeToTextVisitor( SmNode* pNode, OUString &rText ) +{ + pNode->Accept( this ); + maCmdText.stripEnd(' '); + rText = maCmdText.makeStringAndClear(); +} + +void SmNodeToTextVisitor::Visit( SmTableNode* pNode ) +{ + if( pNode->GetToken( ).eType == TBINOM ) { + Append(u"{ binom"); + LineToText( pNode->GetSubNode( 0 ) ); + LineToText( pNode->GetSubNode( 1 ) ); + Append(u"} "); + } else if( pNode->GetToken( ).eType == TSTACK ) { + Append(u"stack{ "); + bool bFirst = true; + for( auto pChild : *pNode ) + { + if(!pChild) + continue; + if(bFirst) + bFirst = false; + else + { + Separate( ); + Append(u"# "); + } + LineToText( pChild ); + } + Separate( ); + Append(u"}"); + } else { //Assume it's a toplevel table, containing lines + bool bFirst = true; + for( auto pChild : *pNode ) + { + if(!pChild) + continue; + if(bFirst) + bFirst = false; + else + { + Separate( ); + Append(u"newline"); + } + Separate( ); + pChild->Accept( this ); + } + } +} + +void SmNodeToTextVisitor::Visit( SmBraceNode* pNode ) +{ + if ( pNode->GetToken().eType == TEVALUATE ) + { + SmNode *pBody = pNode->Body(); + Append(u"evaluate { "); + pBody->Accept( this ); + Append(u"} "); + } + else{ + SmNode *pLeftBrace = pNode->OpeningBrace(), + *pBody = pNode->Body(), + *pRightBrace = pNode->ClosingBrace(); + //Handle special case where it's absolute function + if( pNode->GetToken( ).eType == TABS ) { + Append(u"abs"); + LineToText( pBody ); + } else { + if( pNode->GetScaleMode( ) == SmScaleMode::Height ) + Append(u"left "); + pLeftBrace->Accept( this ); + Separate( ); + pBody->Accept( this ); + Separate( ); + if( pNode->GetScaleMode( ) == SmScaleMode::Height ) + Append(u"right "); + pRightBrace->Accept( this ); + } + } +} + +void SmNodeToTextVisitor::Visit( SmBracebodyNode* pNode ) +{ + for( auto pChild : *pNode ) + { + if(!pChild) + continue; + Separate( ); + pChild->Accept( this ); + } +} + +void SmNodeToTextVisitor::Visit( SmOperNode* pNode ) +{ + Append( pNode->GetToken( ).aText ); + Separate( ); + if( pNode->GetToken( ).eType == TOPER ){ + //There's an SmGlyphSpecialNode if eType == TOPER + if( pNode->GetSubNode( 0 )->GetType( ) == SmNodeType::SubSup ) + Append( pNode->GetSubNode( 0 )->GetSubNode( 0 )->GetToken( ).aText ); + else + Append( pNode->GetSubNode( 0 )->GetToken( ).aText ); + } + if( pNode->GetSubNode( 0 )->GetType( ) == SmNodeType::SubSup ) { + SmSubSupNode *pSubSup = static_cast<SmSubSupNode*>( pNode->GetSubNode( 0 ) ); + SmNode* pChild = pSubSup->GetSubSup( LSUP ); + if( pChild ) { + Separate( ); + Append(u"lsup { "); + LineToText( pChild ); + Append(u"} "); + } + pChild = pSubSup->GetSubSup( LSUB ); + if( pChild ) { + Separate( ); + Append(u"lsub { "); + LineToText( pChild ); + Append(u"} "); + } + pChild = pSubSup->GetSubSup( RSUP ); + if( pChild ) { + Separate( ); + Append(u"^ { "); + LineToText( pChild ); + Append(u"} "); + } + pChild = pSubSup->GetSubSup( RSUB ); + if( pChild ) { + Separate( ); + Append(u"_ { "); + LineToText( pChild ); + Append(u"} "); + } + pChild = pSubSup->GetSubSup( CSUP ); + if( pChild ) { + Separate( ); + if (pSubSup->IsUseLimits()) + Append(u"to { "); + else + Append(u"csup { "); + LineToText( pChild ); + Append(u"} "); + } + pChild = pSubSup->GetSubSup( CSUB ); + if( pChild ) { + Separate( ); + if (pSubSup->IsUseLimits()) + Append(u"from { "); + else + Append(u"csub { "); + LineToText( pChild ); + Append(u"} "); + } + } + LineToText( pNode->GetSubNode( 1 ) ); +} + +void SmNodeToTextVisitor::Visit( SmAlignNode* pNode ) +{ + Append( pNode->GetToken( ).aText ); + LineToText( pNode->GetSubNode( 0 ) ); +} + +void SmNodeToTextVisitor::Visit( SmAttributeNode* pNode ) +{ + Append( pNode->GetToken( ).aText ); + LineToText( pNode->Body() ); +} + +void SmNodeToTextVisitor::Visit( SmFontNode* pNode ) +{ + sal_uInt32 nc; + sal_uInt8 nr, ng, nb; + switch ( pNode->GetToken( ).eType ) + { + case TBOLD: + Append(u"bold "); + break; + case TNBOLD: + Append(u"nbold "); + break; + case TITALIC: + Append(u"italic "); + break; + case TNITALIC: + Append(u"nitalic "); + break; + case TPHANTOM: + Append(u"phantom "); + break; + case TSIZE: + { + Append(u"size "); + switch ( pNode->GetSizeType( ) ) + { + case FontSizeType::PLUS: + Append(u"+"); + break; + case FontSizeType::MINUS: + Append(u"-"); + break; + case FontSizeType::MULTIPLY: + Append(u"*"); + break; + case FontSizeType::DIVIDE: + Append(u"/"); + break; + case FontSizeType::ABSOLUT: + default: + break; + } + Append( ::rtl::math::doubleToUString( + static_cast<double>( pNode->GetSizeParameter( ) ), + rtl_math_StringFormat_Automatic, + rtl_math_DecimalPlaces_Max, '.', true ) ); + Separate( ); + } + break; + + case TDVIPSNAMESCOL: + Append(u"color dvip "); + nc = pNode->GetToken().cMathChar.toUInt32(16); + Append( starmathdatabase::Identify_Color_Parser( nc ).aIdent ); + break; + case THTMLCOL: + case TMATHMLCOL: + case TICONICCOL: + Append(u"color "); + nc = pNode->GetToken().cMathChar.toUInt32(16); + Append( starmathdatabase::Identify_Color_Parser( nc ).aIdent ); + break; + case TRGB: + nc = pNode->GetToken().cMathChar.toUInt32(16); + Append(u"color rgb "); + nb = nc % 256; + nc /= 256; + ng = nc % 256; + nc /= 256; + nr = nc % 256; + Append(OUString::number(nr)); + Separate(); + Append(OUString::number(ng)); + Separate(); + Append(OUString::number(nb)); + Separate(); + break; + case TRGBA: + Append(u"color rgba "); + nc = pNode->GetToken().cMathChar.toUInt32(16); + nb = nc % 256; + nc /= 256; + ng = nc % 256; + nc /= 256; + nr = nc % 256; + nc /= 256; + Append(OUString::number(nr)); + Separate(); + Append(OUString::number(ng)); + Separate(); + Append(OUString::number(nb)); + Separate(); + Append(OUString::number(nc)); + Separate(); + break; + case THEX: + Append(u"color hex "); + nc = pNode->GetToken().cMathChar.toUInt32(16); + Append(OUString::number(nc,16)); + Separate(); + break; + case TSANS: + Append(u"font sans "); + break; + case TSERIF: + Append(u"font serif "); + break; + case TFIXED: + Append(u"font fixed "); + break; + default: + break; + } + LineToText( pNode->GetSubNode( 1 ) ); +} + +void SmNodeToTextVisitor::Visit( SmUnHorNode* pNode ) +{ + if(pNode->GetSubNode( 1 )->GetToken( ).eType == TFACT) + { + // visit children in the reverse order + for( auto it = pNode->rbegin(); it != pNode->rend(); ++it ) + { + auto pChild = *it; + if(!pChild) + continue; + Separate( ); + pChild->Accept( this ); + } + } + else + { + for( auto pChild : *pNode ) + { + if(!pChild) + continue; + Separate( ); + pChild->Accept( this ); + } + } +} + +void SmNodeToTextVisitor::Visit( SmBinHorNode* pNode ) +{ + const SmNode *pParent = pNode->GetParent(); + bool bBraceNeeded = pParent; + SmNode *pLeft = pNode->LeftOperand(), + *pOper = pNode->Symbol(), + *pRight = pNode->RightOperand(); + Separate( ); + if (bBraceNeeded) + Append(u"{ "); + pLeft->Accept( this ); + Separate( ); + pOper->Accept( this ); + Separate( ); + pRight->Accept( this ); + Separate( ); + if (bBraceNeeded) + Append(u"} "); +} + +void SmNodeToTextVisitor::Visit( SmBinVerNode* pNode ) +{ + if( pNode->GetToken().eType == TOVER ){ + SmNode *pNum = pNode->GetSubNode( 0 ), + *pDenom = pNode->GetSubNode( 2 ); + Append(u"{ "); + LineToText( pNum ); + Append(u"over"); + LineToText( pDenom ); + Append(u"} "); + } else{ + SmNode *pNum = pNode->GetSubNode( 0 ), + *pDenom = pNode->GetSubNode( 2 ); + Append(u"{ frac {"); + LineToText( pNum ); + Append(u"} {"); + LineToText( pDenom ); + Append(u"} }"); + } +} + +void SmNodeToTextVisitor::Visit( SmBinDiagonalNode* pNode ) +{ + SmNode *pLeftOperand = pNode->GetSubNode( 0 ), + *pRightOperand = pNode->GetSubNode( 1 ); + Append(u"{ "); + LineToText( pLeftOperand ); + Separate( ); + Append(u"wideslash "); + LineToText( pRightOperand ); + Append(u"} "); +} + +void SmNodeToTextVisitor::Visit( SmSubSupNode* pNode ) +{ + if( pNode->GetToken().eType == TEVALUATE ) + { + Append(u"evaluate { "); + pNode->GetSubNode( 0 )->GetSubNode( 1 )->Accept(this); + Append(u"} "); + SmNode* pChild = pNode->GetSubSup( RSUP ); + if( pChild ) { + Separate( ); + Append(u"to { "); + LineToText( pChild ); + Append(u"} "); + } + pChild = pNode->GetSubSup( RSUB ); + if( pChild ) { + Separate( ); + Append(u"from { "); + LineToText( pChild ); + Append(u"} "); + } + } + else + { + LineToText( pNode->GetBody( ) ); + SmNode *pChild = pNode->GetSubSup( LSUP ); + if( pChild ) { + Separate( ); + Append(u"lsup "); + LineToText( pChild ); + } + pChild = pNode->GetSubSup( LSUB ); + if( pChild ) { + Separate( ); + Append(u"lsub "); + LineToText( pChild ); + } + pChild = pNode->GetSubSup( RSUP ); + if( pChild ) { + Separate( ); + Append(u"^ "); + LineToText( pChild ); + } + pChild = pNode->GetSubSup( RSUB ); + if( pChild ) { + Separate( ); + Append(u"_ "); + LineToText( pChild ); + } + pChild = pNode->GetSubSup( CSUP ); + if( pChild ) { + Separate( ); + if (pNode->IsUseLimits()) + Append(u"to "); + else + Append(u"csup "); + LineToText( pChild ); + } + pChild = pNode->GetSubSup( CSUB ); + if( pChild ) { + Separate( ); + if (pNode->IsUseLimits()) + Append(u"from "); + else + Append(u"csub "); + LineToText( pChild ); + } + } +} + +void SmNodeToTextVisitor::Visit( SmMatrixNode* pNode ) +{ + Append(u"matrix{"); + for (size_t i = 0; i < pNode->GetNumRows(); ++i) + { + for (size_t j = 0; j < pNode->GetNumCols( ); ++j) + { + SmNode* pSubNode = pNode->GetSubNode( i * pNode->GetNumCols( ) + j ); + Separate( ); + if (pSubNode) + pSubNode->Accept( this ); + Separate( ); + if (j != pNode->GetNumCols() - 1U) + Append(u"#"); + } + Separate( ); + if (i != pNode->GetNumRows() - 1U) + Append(u"##"); + } + Append(u"} "); +} + +void SmNodeToTextVisitor::Visit( SmPlaceNode* ) +{ + Append(u"<?>"); +} + +void SmNodeToTextVisitor::Visit( SmTextNode* pNode ) +{ + SmTokenType type = pNode->GetToken( ).eType; + switch(type){ + case TTEXT: + Append(u"\""); + Append( pNode->GetToken().aText ); + Append(u"\""); + break; + case TNUMBER: + Append( pNode->GetToken().aText ); + break; + case TIDENT: + Append( pNode->GetToken().aText ); + break; + case TFUNC: + Append(u"func "); + Append( pNode->GetToken().aText ); + break; + case THEX: + Append(u"hex "); + Append( pNode->GetToken().aText ); + break; + default: + Append( pNode->GetToken().aText ); + } + Separate( ); +} + +void SmNodeToTextVisitor::Visit( SmSpecialNode* pNode ) +{ + SmTokenType type = pNode->GetToken().eType; + switch(type){ + case TLIMSUP: + Append(u"lim sup "); + break; + case TLIMINF: + Append(u"lim inf "); + break; + default: + Append( pNode->GetToken().aText ); + break; + } +} + +void SmNodeToTextVisitor::Visit( SmGlyphSpecialNode* pNode ) +{ + if( pNode->GetToken( ).eType == TBOPER ) + Append(u"boper "); + else + Append(u"uoper "); + Append( pNode->GetToken( ).aText ); +} + +//TODO to improve this it is required to improve mathmlimport. +void SmNodeToTextVisitor::Visit( SmMathSymbolNode* pNode ) +{ + if ( ( pNode->GetToken().nGroup & TG::LBrace ) + || ( pNode->GetToken().nGroup & TG::RBrace ) + || ( pNode->GetToken().nGroup & TG::Sum ) + || ( pNode->GetToken().nGroup & TG::Product ) + || ( pNode->GetToken().nGroup & TG::Relation ) + || ( pNode->GetToken().nGroup & TG::UnOper ) + || ( pNode->GetToken().nGroup & TG::Oper ) + ) { + Append( pNode->GetToken().aText ); + return; + } + sal_Unicode cChar = pNode->GetToken().cMathChar[0]; + Separate( ); + switch(cChar){ + case MS_NONE: + Append(u"none"); + break; + case '{': + Append(u"{"); + break; + case '}': + Append(u"}"); + break; + case MS_VERTLINE: + Append(u"mline"); + break; + case MS_TILDE: + Append(u"\"~\""); + break; + case MS_RIGHTARROW: + if( pNode->GetToken().eType == TTOWARD ) Append(u"toward"); + else Append(u"rightarrow"); + break; + case MS_LEFTARROW: + Append(u"leftarrow"); + break; + case MS_UPARROW: + Append(u"uparrow"); + break; + case MS_DOWNARROW: + Append(u"downarrow"); + break; + case MS_LAMBDABAR: + Append(u"lambdabar"); + break; + case MS_DOTSLOW: + Append(u"dotslow"); + break; + case MS_SETC: + Append(u"setC"); + break; + case MS_HBAR: + Append(u"hbar"); + break; + case MS_IM: + Append(u"Im"); + break; + case MS_SETN: + Append(u"setN"); + break; + case MS_WP: + Append(u"wp"); + break; + case MS_LAPLACE: + Append(u"laplace"); + break; + case MS_SETQ: + Append(u"setQ"); + break; + case MS_RE: + Append(u"Re"); + break; + case MS_SETR: + Append(u"setR"); + break; + case MS_SETZ: + Append(u"setZ"); + break; + case MS_ALEPH: + Append(u"aleph"); + break; + case 0x0362: + Append(u"widevec"); + break; + case MS_DLARROW: + Append(u"dlarrow"); + break; + case MS_DRARROW: + Append(u"drarrow"); + break; + case MS_DLRARROW: + Append(u"dlrarrow"); + break; + case MS_FORALL: + Append(u"forall"); + break; + case MS_PARTIAL: + Append(u"partial"); + break; + case MS_EXISTS: + Append(u"exists"); + break; + case MS_NOTEXISTS: + Append(u"notexists"); + break; + case MS_EMPTYSET: + Append(u"emptyset"); + break; + case MS_NABLA: + Append(u"nabla"); + break; + case MS_BACKEPSILON: + Append(u"backepsilon"); + break; + case MS_CIRC: + Append(u"circ"); + break; + case MS_INFINITY: + Append(u"infinity"); + break; + case 0x22b2: // NORMAL SUBGROUP OF + Append(OUStringChar(cChar)); + break; + case 0x22b3: // CONTAINS AS NORMAL SUBGROUP + Append(OUStringChar(cChar)); + break; + case MS_ORTHO: + Append(u"ortho"); + break; + case MS_DOTSVERT: + Append(u"dotsvert"); + break; + case MS_DOTSAXIS: + Append(u"dotsaxis"); + break; + case MS_DOTSUP: + Append(u"dotsup"); + break; + case MS_DOTSDOWN: + Append(u"dotsdown"); + break; + case '^': + Append(u"^"); + break; + case 0xe091: + Append(u"widehat"); + break; + case 0xe096: + Append(u"widetilde"); + break; + case 0xe098: + Append(u"widevec"); + break; + case 0xeb01: //no space + case 0xeb08: //normal space + break; + case 0xef04: //tiny space + case 0xef05: //tiny space + case 0xeb02: //small space + case 0xeb04: //medium space + Append(u"`"); + break; + case 0xeb05: //large space + Append(u"~"); + break; + case 0x3a9: + Append(u"%OMEGA"); + break; + default: + Append(OUStringChar(cChar)); + break; + } +} + +void SmNodeToTextVisitor::Visit( SmBlankNode* pNode ) +{ + sal_uInt16 nNum = pNode->GetBlankNum(); + if (nNum <= 0) + return; + sal_uInt16 nWide = nNum / 4; + sal_uInt16 nNarrow = nNum % 4; + for (sal_uInt16 i = 0; i < nWide; i++) + Append(u"~"); + for (sal_uInt16 i = 0; i < nNarrow; i++) + Append(u"`"); + Append(u" "); +} + +void SmNodeToTextVisitor::Visit( SmErrorNode* ) +{ + // Add something for error nodes so that we can parse this back. + Append(u"{}"); +} + +void SmNodeToTextVisitor::Visit( SmLineNode* pNode ) +{ + for( auto pChild : *pNode ) + { + if(!pChild) + continue; + Separate( ); + pChild->Accept( this ); + } +} + +void SmNodeToTextVisitor::Visit( SmExpressionNode* pNode ) +{ + bool bracketsNeeded = pNode->GetNumSubNodes() != 1; + if (!bracketsNeeded) + { + const SmNode *pParent = pNode->GetParent(); + // nested subsups + bracketsNeeded = + pParent && pParent->GetType() == SmNodeType::SubSup && + pNode->GetNumSubNodes() == 1 && + pNode->GetSubNode(0)->GetType() == SmNodeType::SubSup; + } + + if (bracketsNeeded) { + Append(u"{ "); + } + for( auto pChild : *pNode ) + { + if(!pChild) + continue; + pChild->Accept( this ); + Separate( ); + } + if (bracketsNeeded) { + Append(u"} "); + } +} + +void SmNodeToTextVisitor::Visit( SmPolyLineNode* ) +{ +} + +void SmNodeToTextVisitor::Visit( SmRootNode* pNode ) +{ + SmNode *pExtra = pNode->GetSubNode( 0 ), + *pBody = pNode->GetSubNode( 2 ); + if( pExtra ) { + Append(u"nroot"); + LineToText( pExtra ); + } else + Append(u"sqrt"); + LineToText( pBody ); +} + +void SmNodeToTextVisitor::Visit( SmRootSymbolNode* ) +{ +} + +void SmNodeToTextVisitor::Visit( SmRectangleNode* ) +{ +} + +void SmNodeToTextVisitor::Visit( SmVerticalBraceNode* pNode ) +{ + SmNode *pBody = pNode->Body(), + *pScript = pNode->Script(); + LineToText( pBody ); + Append( pNode->GetToken( ).aText ); + LineToText( pScript ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/wordexportbase.cxx b/starmath/source/wordexportbase.cxx new file mode 100644 index 0000000000..adf1a275c3 --- /dev/null +++ b/starmath/source/wordexportbase.cxx @@ -0,0 +1,184 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include "wordexportbase.hxx" +#include <sal/log.hxx> +#include <osl/diagnose.h> + +SmWordExportBase::SmWordExportBase(const SmNode* pIn) + : m_pTree(pIn) +{ +} + +SmWordExportBase::~SmWordExportBase() = default; + +void SmWordExportBase::HandleNode(const SmNode* pNode, int nLevel) +{ + SAL_INFO("starmath.wordbase", + "Node: " << nLevel << " " << int(pNode->GetType()) << " " << pNode->GetNumSubNodes()); + switch (pNode->GetType()) + { + case SmNodeType::Attribute: + HandleAttribute(static_cast<const SmAttributeNode*>(pNode), nLevel); + break; + case SmNodeType::Text: + HandleText(pNode, nLevel); + break; + case SmNodeType::VerticalBrace: + HandleVerticalBrace(static_cast<const SmVerticalBraceNode*>(pNode), nLevel); + break; + case SmNodeType::Brace: + HandleBrace(static_cast<const SmBraceNode*>(pNode), nLevel); + break; + case SmNodeType::Oper: + HandleOperator(static_cast<const SmOperNode*>(pNode), nLevel); + break; + case SmNodeType::UnHor: + HandleUnaryOperation(static_cast<const SmUnHorNode*>(pNode), nLevel); + break; + case SmNodeType::BinHor: + HandleBinaryOperation(static_cast<const SmBinHorNode*>(pNode), nLevel); + break; + case SmNodeType::BinVer: + HandleFractions(pNode, nLevel, nullptr); + break; + case SmNodeType::Root: + HandleRoot(static_cast<const SmRootNode*>(pNode), nLevel); + break; + case SmNodeType::Special: + { + auto pText = static_cast<const SmTextNode*>(pNode); + //if the token str and the result text are the same then this + //is to be seen as text, else assume it's a mathchar + if (pText->GetText() == pText->GetToken().aText) + HandleText(pText, nLevel); + else + HandleMath(pText, nLevel); + break; + } + case SmNodeType::Math: + case SmNodeType::MathIdent: + HandleMath(pNode, nLevel); + break; + case SmNodeType::SubSup: + HandleSubSupScript(static_cast<const SmSubSupNode*>(pNode), nLevel); + break; + case SmNodeType::Expression: + HandleAllSubNodes(pNode, nLevel); + break; + case SmNodeType::Table: + //Root Node, PILE equivalent, i.e. vertical stack + HandleTable(pNode, nLevel); + break; + case SmNodeType::Matrix: + HandleMatrix(static_cast<const SmMatrixNode*>(pNode), nLevel); + break; + case SmNodeType::Line: + { + // TODO + HandleAllSubNodes(pNode, nLevel); + } + break; +#if 0 + case SmNodeType::Align: + HandleMAlign(pNode,nLevel); + break; +#endif + case SmNodeType::Place: + // explicitly do nothing, MSOffice treats that as a placeholder if item is missing + break; + case SmNodeType::Blank: + HandleBlank(); + break; + default: + HandleAllSubNodes(pNode, nLevel); + break; + } +} + +//Root Node, PILE equivalent, i.e. vertical stack +void SmWordExportBase::HandleTable(const SmNode* pNode, int nLevel) +{ + //The root of the starmath is a table, if + //we convert this them each iteration of + //conversion from starmath to Word will + //add an extra unnecessary level to the + //Word output stack which would grow + //without bound in a multi step conversion + if (nLevel || pNode->GetNumSubNodes() > 1) + HandleVerticalStack(pNode, nLevel); + else + HandleAllSubNodes(pNode, nLevel); +} + +void SmWordExportBase::HandleAllSubNodes(const SmNode* pNode, int nLevel) +{ + int size = pNode->GetNumSubNodes(); + for (int i = 0; i < size; ++i) + { + // TODO remove when all types of nodes are handled properly + if (pNode->GetSubNode(i) == nullptr) + { + SAL_WARN("starmath.wordbase", "Subnode is NULL, parent node not handled properly"); + continue; + } + HandleNode(pNode->GetSubNode(i), nLevel + 1); + } +} + +void SmWordExportBase::HandleUnaryOperation(const SmUnHorNode* pNode, int nLevel) +{ + // update HandleMath() when adding new items + SAL_INFO("starmath.wordbase", "Unary: " << int(pNode->GetToken().eType)); + + HandleAllSubNodes(pNode, nLevel); +} + +void SmWordExportBase::HandleBinaryOperation(const SmBinHorNode* pNode, int nLevel) +{ + SAL_INFO("starmath.wordbase", "Binary: " << int(pNode->Symbol()->GetToken().eType)); + // update HandleMath() when adding new items + switch (pNode->Symbol()->GetToken().eType) + { + case TDIVIDEBY: + return HandleFractions(pNode, nLevel, "lin"); + default: + HandleAllSubNodes(pNode, nLevel); + break; + } +} + +void SmWordExportBase::HandleMath(const SmNode* pNode, int nLevel) +{ + SAL_INFO("starmath.wordbase", "Math: " << int(pNode->GetToken().eType)); + switch (pNode->GetToken().eType) + { + case TDIVIDEBY: + case TACUTE: + OSL_ASSERT(false); + [[fallthrough]]; // the above are handled elsewhere, e.g. when handling BINHOR + default: + HandleText(pNode, nLevel); + break; + } +} + +void SmWordExportBase::HandleSubSupScript(const SmSubSupNode* pNode, int nLevel) +{ + // set flags to a bitfield of which sub/sup items exists + int flags = (pNode->GetSubSup(CSUB) != nullptr ? (1 << CSUB) : 0) + | (pNode->GetSubSup(CSUP) != nullptr ? (1 << CSUP) : 0) + | (pNode->GetSubSup(RSUB) != nullptr ? (1 << RSUB) : 0) + | (pNode->GetSubSup(RSUP) != nullptr ? (1 << RSUP) : 0) + | (pNode->GetSubSup(LSUB) != nullptr ? (1 << LSUB) : 0) + | (pNode->GetSubSup(LSUP) != nullptr ? (1 << LSUP) : 0); + HandleSubSupScriptInternal(pNode, nLevel, flags); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/starmath/source/wordexportbase.hxx b/starmath/source/wordexportbase.hxx new file mode 100644 index 0000000000..33a179d05f --- /dev/null +++ b/starmath/source/wordexportbase.hxx @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#pragma once + +#include <node.hxx> + +/** + Base class implementing writing of formulas to Word. + */ +class SmWordExportBase +{ +public: + explicit SmWordExportBase(const SmNode* pIn); + virtual ~SmWordExportBase(); + +protected: + void HandleNode(const SmNode* pNode, int nLevel); + void HandleAllSubNodes(const SmNode* pNode, int nLevel); + void HandleTable(const SmNode* pNode, int nLevel); + virtual void HandleVerticalStack(const SmNode* pNode, int nLevel) = 0; + virtual void HandleText(const SmNode* pNode, int nLevel) = 0; + void HandleMath(const SmNode* pNode, int nLevel); + virtual void HandleFractions(const SmNode* pNode, int nLevel, const char* type) = 0; + void HandleUnaryOperation(const SmUnHorNode* pNode, int nLevel); + void HandleBinaryOperation(const SmBinHorNode* pNode, int nLevel); + virtual void HandleRoot(const SmRootNode* pNode, int nLevel) = 0; + virtual void HandleAttribute(const SmAttributeNode* pNode, int nLevel) = 0; + virtual void HandleOperator(const SmOperNode* pNode, int nLevel) = 0; + void HandleSubSupScript(const SmSubSupNode* pNode, int nLevel); + virtual void HandleSubSupScriptInternal(const SmSubSupNode* pNode, int nLevel, int flags) = 0; + virtual void HandleMatrix(const SmMatrixNode* pNode, int nLevel) = 0; + virtual void HandleBrace(const SmBraceNode* pNode, int nLevel) = 0; + virtual void HandleVerticalBrace(const SmVerticalBraceNode* pNode, int nLevel) = 0; + virtual void HandleBlank() = 0; + const SmNode* GetTree() const { return m_pTree; } + +private: + const SmNode* const m_pTree; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |