/* This file is part of the wvWare 2 project
   SPDX-FileCopyrightText: 2001-2003 Werner Trobin <trobin@kde.org>
   SPDX-FileCopyrightText: 2010-2011 Matus Uzak <matus.uzak@ixonos.com>

   This library is free software; you can redistribute it and/or
   modify it under the terms of the Library GNU General Public
   version 2 of the License, or (at your option) version 3 or,
   at the discretion of KDE e.V (which shall act as a proxy as in
   section 14 of the GPLv3), any later version..

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this library; see the file COPYING.LIB.  If not, write to
   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
   Boston, MA 02110-1301, USA.
*/

#include "word97_helper.h"
#include "convert.h"
#include "olestream.h"
#include "paragraphproperties.h"
#include "styles.h"
#include "word95_generated.h"
#include "word95_helper.h"
#include "word97_generated.h"

#include <algorithm>
#include <cstdlib>
#include <functional>
#include <string.h> // memcpy

#include "wvlog.h"

namespace wvWare
{

namespace Word97
{

// The fts enumeration specifies how the preferred width for a table, table
// indent, table cell, cell margin, or cell spacing is defined.  MS-DOC, p.362
typedef enum {
    ftsNil = 0x00, // the size is undefined and must be ignored
    ftsAuto = 0x01, // no preferred width is specified
    ftsPercent = 0x02, // preferred width is measured in units of 1/50 of a percent
    ftsDxa = 0x03, // absolute width measured in twips
    ftsDxaSys = 0x13 // absolute width measured in twips
} FTS;

// Make use of the grfbrc enumeration in decision to which cell sides the cell
// margin or cell spacing applies to.
typedef enum {
    fbrcTop = 0x01,
    fbrcLeft = 0x02,
    fbrcBottom = 0x04,
    fbrcRight = 0x08
} GRFBRC;

namespace SPRM
{

struct opcodeBits {
    U16 ispmd : 9;
    U16 fSpec : 1;
    U16 sgc : 3;
    U16 spra : 3;
};

// Apart from all the SPRMs in the documentation I added some from the OOo code.
// Additionally there are some xyzFake entries, which are used to get Word 6 sprms
// through the same code :-}
typedef enum {
    sprmNoop = 0x0000,
    sprmCFRMarkDel = 0x0800,
    sprmCFRMark = 0x0801,
    sprmCFFldVanish = 0x0802,
    sprmCFData = 0x0806,
    sprmCFOle2 = 0x080A,
    sprmCFBold = 0x0835,
    sprmCFItalic = 0x0836,
    sprmCFStrike = 0x0837,
    sprmCFOutline = 0x0838,
    sprmCFShadow = 0x0839,
    sprmCFSmallCaps = 0x083A,
    sprmCFCaps = 0x083B,
    sprmCFVanish = 0x083C,
    sprmCFImprint = 0x0854,
    sprmCFSpec = 0x0855,
    sprmCFObj = 0x0856,
    sprmCFEmboss = 0x0858,
    sprmCFBiDi = 0x085A,
    sprmCFDiacColor = 0x085B,
    sprmCFBoldBi = 0x085C,
    sprmCFItalicBi = 0x085D,
    sprmCFUsePgsuSettings = 0x0868,
    sprmCUndocumented1 = 0x875,
    sprmPJc = 0x2403,
    sprmPJcFE = 0x2461, // Undocumented. According to OOo it's the asian equivalent to sprmPJc
    sprmPFSideBySide = 0x2404,
    sprmPFKeep = 0x2405,
    sprmPFKeepFollow = 0x2406,
    sprmPFPageBreakBefore = 0x2407,
    sprmPBrcl = 0x2408,
    sprmPBrcp = 0x2409,
    sprmPFNoLineNumb = 0x240C,
    sprmPFInTable = 0x2416,
    sprmPFTtp = 0x2417,
    sprmPWr = 0x2423,
    sprmPFNoAutoHyph = 0x242A,
    sprmPFLocked = 0x2430,
    sprmPFWidowControl = 0x2431,
    sprmPFKinsoku = 0x2433,
    sprmPFWordWrap = 0x2434,
    sprmPFOverflowPunct = 0x2435,
    sprmPFTopLinePunct = 0x2436,
    sprmPFAutoSpaceDE = 0x2437,
    sprmPFAutoSpaceDN = 0x2438,
    sprmPISnapBaseLine = 0x243B,
    sprmPFBiDi = 0x2441,
    sprmPFNumRMIns = 0x2443,
    sprmPCrLf = 0x2444,
    sprmPFUsePgsuSettings = 0x2447,
    sprmPFAdjustRight = 0x2448,
    sprmPFInnerTableCell = 0x244B,
    sprmPFInnerTtp = 0x244C,
    sprmPFDyaBeforeAuto = 0x245B,
    sprmPFDyaAfterAuto = 0x245C,
    sprmPNLvlAnmFake = 0x25FF, // Fake entry!
    sprmPIncLvl = 0x2602,
    sprmPIlvl = 0x260A,
    sprmPPc = 0x261B,
    sprmPOutLvl = 0x2640,
    sprmCSfxText = 0x2859,
    sprmCIdctHint = 0x286F,
    sprmCHighlight = 0x2A0C,
    sprmCFFtcAsciSymb = 0x2A10,
    sprmCDefault = 0x2A32,
    sprmCPlain = 0x2A33,
    sprmCKcd = 0x2A34,
    sprmCKul = 0x2A3E,
    sprmCIco = 0x2A42,
    sprmCHpsInc = 0x2A44,
    sprmCHpsPosAdj = 0x2A46,
    sprmCIss = 0x2A48,
    sprmCFDStrike = 0x2A53,
    sprmPicBrcl = 0x2E00,
    sprmScnsPgn = 0x3000,
    sprmSiHeadingPgn = 0x3001,
    sprmSFEvenlySpaced = 0x3005,
    sprmSFProtected = 0x3006,
    sprmSBkc = 0x3009,
    sprmSFTitlePage = 0x300A,
    sprmSFAutoPgn = 0x300D,
    sprmSNfcPgn = 0x300E,
    sprmSFPgnRestart = 0x3011,
    sprmSFEndnote = 0x3012,
    sprmSLnc = 0x3013,
    sprmSGprfIhdt = 0x3014, // MS Word 6.0, header/footer related group of bit flags
    sprmSLBetween = 0x3019,
    sprmSVjc = 0x301A,
    sprmSBOrientation = 0x301D,
    sprmSBCustomize = 0x301E,
    sprmSFBiDi = 0x3228,
    sprmSFFacingCol = 0x3229,
    sprmSFRTLGutter = 0x322A,
    sprmTFCantSplit = 0x3403,
    sprmTTableHeader = 0x3404,
    sprmTPc = 0x360D,
    sprmTUndocumented1 = 0x3615,
    sprmPWHeightAbs = 0x442B,
    sprmPDcs = 0x442C,
    sprmPShd80 = 0x442D,
    sprmPWAlignFont = 0x4439,
    sprmPFrameTextFlow = 0x443A,
    sprmPIstd = 0x4600,
    sprmPIlfo = 0x460B,
    sprmPNest = 0x4610,
    sprmPBrcTop10 = 0x461C,
    sprmPBrcLeft10 = 0x461D,
    sprmPBrcBottom10 = 0x461E,
    sprmPBrcRight10 = 0x461F,
    sprmPBrcBetween10 = 0x4620,
    sprmPBrcBar10 = 0x4621,
    sprmPDxaFromText10 = 0x4622,
    sprmCIbstRMark = 0x4804,
    sprmCIdslRMark = 0x4807,
    sprmCIdCharType = 0x480B,
    sprmCHpsPos = 0x4845,
    sprmCHpsKern = 0x484B,
    sprmCYsri = 0x484E,
    sprmCCharScale = 0x4852,
    sprmCLidBi = 0x485F,
    sprmCIbstRMarkDel = 0x4863,
    sprmCShd80 = 0x4866,
    sprmCIdslRMarkDel = 0x4867,
    sprmCCpg = 0x486B,
    sprmCRgLid0 = 0x486D,
    sprmCRgLid1 = 0x486E,
    sprmCRgLidUndocumented1 = 0x4873, // According to OOo it's equal to sprmCRgLid0
    sprmCUndocumented2 = 0x4874,
    sprmCPbiGrf = 0x4888,
    sprmCIstd = 0x4A30,
    sprmCFtcDefault = 0x4A3D,
    sprmCLid = 0x4A41,
    sprmCHps = 0x4A43,
    sprmCHpsMul = 0x4A4D,
    sprmCRgFtc0 = 0x4A4F,
    sprmCRgFtc1 = 0x4A50,
    sprmCRgFtc2 = 0x4A51,
    sprmCFtcBi = 0x4A5E,
    sprmCIcoBi = 0x4A60,
    sprmCHpsBi = 0x4A61,
    sprmSDmBinFirst = 0x5007,
    sprmSDmBinOther = 0x5008,
    sprmSCcolumns = 0x500B,
    sprmSNLnnMod = 0x5015,
    sprmSLnnMin = 0x501B,
    sprmSPgnStart = 0x501C,
    sprmSDmPaperReq = 0x5026,
    sprmSClm = 0x5032,
    sprmSNfcFtnRef = 0x5040,
    sprmSNfcEdnRef = 0x5042,
    sprmSTextFlow = 0x5033,
    sprmSPgbProp = 0x522F,
    sprmTJc = 0x5400,
    sprmTFBiDi = 0x560B,
    sprmTDelete = 0x5622,
    sprmTMerge = 0x5624,
    sprmTSplit = 0x5625,
    sprmTUndocumented2 = 0x5664,
    sprmPDyaLine = 0x6412,
    sprmPBrcTop = 0x6424,
    sprmPBrcLeft = 0x6425,
    sprmPBrcBottom = 0x6426,
    sprmPBrcRight = 0x6427,
    sprmPBrcBetween = 0x6428,
    sprmPTableProps = 0x646b,
    sprmPBrcBar = 0x6629,
    sprmPHugePapx = 0x6645,
    sprmPHugePapx2 = 0x6646,
    sprmPItap = 0x6649,
    sprmPDtap = 0x664A,
    sprmCDttmRMark = 0x6805,
    sprmCObjLocation = 0x680E,
    sprmCRsidProp = 0x6815,
    sprmCDttmRMarkDel = 0x6864,
    sprmCBrc = 0x6865,
    sprmCCv = 0x6870,
    sprmCCvUl = 0x6877,
    sprmCPbiIBullet = 0x6887,
    sprmCPicLocation = 0x6A03,
    sprmCSymbol = 0x6A09,
    sprmPicBrcTop = 0x6C02,
    sprmPicBrcLeft = 0x6C03,
    sprmPicBrcBottom = 0x6C04,
    sprmPicBrcRight = 0x6C05,
    sprmSBrcTop = 0x702B,
    sprmSBrcLeft = 0x702C,
    sprmSBrcBottom = 0x702D,
    sprmSBrcRight = 0x702E,
    sprmSDxtCharSpace = 0x7030,
    sprmTTlp = 0x740A,
    sprmTHTMLProps = 0x740C,
    sprmTInsert = 0x7621,
    sprmTDxaCol = 0x7623,
    sprmTSetShd = 0x7627,
    sprmTSetShdOdd = 0x7628,
    sprmTTextFlow = 0x7629,
    sprmPDxaRight = 0x840E,
    sprmPDxaRightFE = 0x845D, // Undocumented. According to OOo it's the asian equivalent to sprmPDxaRight
    sprmPDxaLeft = 0x840F,
    sprmPDxaLeftFE = 0x845E, // Undocumented. According to OOo it's the asian equivalent to sprmPDxaLeft
    sprmPDxaLeft1 = 0x8411,
    sprmPDxaLeft1FE = 0x8460, // Undocumented. According to OOo it's the asian equivalent to sprmPDxaLeft1
    sprmPDxaAbs = 0x8418,
    sprmPDyaAbs = 0x8419,
    sprmPDxaWidth = 0x841A,
    sprmPDyaFromText = 0x842E,
    sprmPDxaFromText = 0x842F,
    sprmCDxaSpace = 0x8840,
    sprmSDxaColumns = 0x900C,
    sprmSDxaLnn = 0x9016,
    sprmSDyaTop = 0x9023,
    sprmSDyaBottom = 0x9024,
    sprmSDyaLinePitch = 0x9031,
    sprmTDyaRowHeight = 0x9407,
    sprmTDxaFromText = 0x9410,
    sprmTDyaFromText = 0x9411,
    sprmTDxaFromTextRight = 0x941E,
    sprmTDyaFromTextBottom = 0x941F,
    sprmTDxaAbs = 0x940E,
    sprmTDyaAbs = 0x940F,
    sprmTDxaLeft = 0x9601,
    sprmTDxaGapHalf = 0x9602,
    sprmPDyaBefore = 0xA413,
    sprmPDyaAfter = 0xA414,
    sprmSDyaPgn = 0xB00F,
    sprmSDxaPgn = 0xB010,
    sprmSDyaHdrTop = 0xB017,
    sprmSDyaHdrBottom = 0xB018,
    sprmSXaPage = 0xB01F,
    sprmSYaPage = 0xB020,
    sprmSDxaLeft = 0xB021,
    sprmSDxaRight = 0xB022,
    sprmSDzaGutter = 0xB025,
    sprmPIstdPermute = 0xC601,
    sprmPChgTabsPapx = 0xC60D,
    sprmPChgTabs = 0xC615,
    sprmPRuler = 0xC632,
    sprmPAnld = 0xC63E,
    sprmPPropRMark = 0xC63F,
    sprmPNumRM = 0xC645,
    sprmPShd = 0xC64D,
    sprmPUndocumented1 = 0xC64E,
    sprmPUndocumented2 = 0xC64F,
    sprmPUndocumented3 = 0xC650,
    sprmPUndocumented4 = 0xC651,
    sprmCIstdPermute = 0xCA31,
    sprmCMajority = 0xCA47,
    sprmCHpsNew50 = 0xCA49,
    sprmCHpsInc1 = 0xCA4A,
    sprmCMajority50 = 0xCA4C,
    sprmCPropRMark = 0xCA57,
    sprmCDispFldRMark = 0xCA62,
    sprmCShd = 0xCA71,
    sprmCFELayout = 0xCA78,
    sprmPicScale = 0xCE01,
    sprmSOlstAnm = 0xD202,
    sprmSPropRMark = 0xD227,
    sprmTTableBorders80 = 0xD605,
    sprmTDefTable10 = 0xD606,
    sprmTDefTable = 0xD608,
    sprmTDefTableShd80 = 0xD609,
    sprmTDefTableShd = 0xD612,
    sprmTTableBorders = 0xD613,
    sprmTDefTableShd2nd = 0xD616,
    sprmTDefTableShd3rd = 0xD60C,
    sprmTBrcTopCv = 0xD61A,
    sprmTBrcLeftCv = 0xD61B,
    sprmTBrcBottomCv = 0xD61C,
    sprmTBrcRightCv = 0xD61D,
    sprmTSetBrc80 = 0xD620,
    sprmTSetBrc10 = 0xD626,
    sprmTDiagLine = 0xD62A,
    sprmTVertMerge = 0xD62B,
    sprmTVertAlign = 0xD62C,
    sprmTSetBrc = 0xD62F,
    sprmTCellPadding = 0xD632,
    sprmTCellSpacingDefault = 0xD633,
    sprmTCellPaddingDefault = 0xD634,
    sprmTSetShdTable = 0xD660,
    sprmTCellBrcType = 0xD662,
    sprmCChs = 0xEA08,
    sprmCSizePos = 0xEA3F,
    sprmSDxaColWidth = 0xF203,
    sprmSDxaColSpacing = 0xF204,
    sprmTUndocumented10 = 0xF614,
    sprmTUndocumented11 = 0xF617,
    sprmTUndocumented12 = 0xF618,
    sprmTWidthIndent = 0xF661
} opcodes;

// The length of the SPRM parameter
U16 determineParameterLength(U16 sprm, const U8 *in, WordVersion version)
{
    if (version == Word8) {
        static const char operandSizes[8] = {1, 1, 2, 4, 2, 2, 0, 3};

        int index = (sprm & 0xE000) >> 13;
        if (operandSizes[index] != 0) {
#ifdef WV2_DEBUG_SPRMS
            wvlog << "==> Size of the sprm argument:" << (U16)operandSizes[index] << Qt::endl;
            wvlog << "sgc:" << (U16)((sprm & 0x1C00) >> 10);
#endif
            return operandSizes[index];
        } else {
#ifdef WV2_DEBUG_SPRMS
            wvlog << "==> Variable size of the sprm argument:";
#endif
            // Get length of variable size operand.
            switch (sprm) {
            case sprmTDefTable10:
            case sprmTDefTable:
                return readU16(in) + 1;
                break;
            case sprmPChgTabs:
                if (*in == 255) {
                    U8 itbdDelMax = in[1];
                    U8 itbdAddMax = in[1 + itbdDelMax * 4];
                    return 1 + itbdDelMax * 4 + itbdAddMax * 3;
                } else
                    return *in + 1;
                break;
            default:
                return *in + 1;
                break;
            }
        }
    } else { // Word67
        if (sprm > 255)
            wvlog << "Error: Trying to get the length of a flaky SPRM (" << sprm << ", 0x" << Qt::hex << sprm << Qt::dec << ") via the Word 95 method!"
                  << Qt::endl;
        return Word95::SPRM::determineParameterLength(static_cast<U8>(sprm), in);
    }
}

// Apply a <T> grpprl of a given size ("count" bytes long)
// This template function might be a bit sick, but it helps to
// avoid duplicated code, so what ;)
template<class T>
void apply(T *const t,
           S16 (T::*applySPRM)(const U8 *, const Style *, const StyleSheet *, OLEStreamReader *, WordVersion),
           const U8 *grpprl,
           U16 count,
           const Style *style,
           const StyleSheet *styleSheet,
           OLEStreamReader *dataStream,
           WordVersion version)
{
    if (!grpprl)
        return;

    // We are using an integer here, that we can detect situations where
    // we read beyond the limit due to a buggy spec/impl.
    // The plain U16 would overflow and we'd be trapped in a loop.
    int safeCount = count;

    // walk through the grpprl, applying one sprm after the other
    while (safeCount > 1) {
        S16 result = (t->*applySPRM)(grpprl, style, styleSheet, dataStream, version);
        if (result == -1) {
            U16 sprm;
            if (version == Word8) {
                sprm = readU16(grpprl);
                grpprl += 2;
#ifdef WV2_DEBUG_SPRMS
                wvlog << "Seems like that's a different SPRM (0x" << hex << sprm << dec << ")... skipping" << Qt::endl;
#endif
            } else {
                sprm = *grpprl++;
#ifdef WV2_DEBUG_SPRMS
                wvlog << "Seems like that's a different SPRM (" << sprm << ")... skipping" << Qt::endl;
#endif
            }

            U16 len = determineParameterLength(sprm, grpprl, version);
            grpprl += len;
            safeCount -= len + (version == Word8 ? 2 : 1);
        } else {
            grpprl += result;
            safeCount -= result;
        }
    }
    if (safeCount < 0)
        wvlog << "Warning: We read past the end of the grpprl, buggy spec?" << Qt::endl;
}

U16 unzippedOpCode(U8 isprm)
{
    // Note: Changed sprmCFStrikeRM to sprmCFStrike and sprmPPnbrRMarkNot to sprmNoop
    static const U16 lut[] = {sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmPIncLvl,
                              sprmPJc,
                              sprmPFSideBySide,
                              sprmPFKeep,
                              sprmPFKeepFollow,
                              sprmPFPageBreakBefore,
                              sprmPBrcl,
                              sprmPBrcp,
                              sprmPIlvl,
                              sprmNoop,
                              sprmPFNoLineNumb,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmPFInTable,
                              sprmPFTtp,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmPPc,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmPWr,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmPFNoAutoHyph,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmPFLocked,
                              sprmPFWidowControl,
                              sprmNoop,
                              sprmPFKinsoku,
                              sprmPFWordWrap,
                              sprmPFOverflowPunct,
                              sprmPFTopLinePunct,
                              sprmPFAutoSpaceDE,
                              sprmPFAutoSpaceDN,
                              sprmNoop,
                              sprmNoop,
                              sprmPISnapBaseLine,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmCFStrike,
                              sprmCFRMark,
                              sprmCFFldVanish,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmCFData,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmCFOle2,
                              sprmNoop,
                              sprmCHighlight,
                              sprmCFEmboss,
                              sprmCSfxText,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmCPlain,
                              sprmNoop,
                              sprmCFBold,
                              sprmCFItalic,
                              sprmCFStrike,
                              sprmCFOutline,
                              sprmCFShadow,
                              sprmCFSmallCaps,
                              sprmCFCaps,
                              sprmCFVanish,
                              sprmNoop,
                              sprmCKul,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmCIco,
                              sprmNoop,
                              sprmCHpsInc,
                              sprmNoop,
                              sprmCHpsPosAdj,
                              sprmNoop,
                              sprmCIss,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmCFDStrike,
                              sprmCFImprint,
                              sprmCFSpec,
                              sprmCFObj,
                              sprmPicBrcl,
                              sprmPOutLvl,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop};

    return lut[isprm];
}

/**
 * Converts a Word 6/7 SPRM to a Word 8 one.
 */
U16 word6toWord8(U8 sprm)
{
    static const U16 lut[] = {sprmNoop,
                              sprmNoop,
                              sprmPIstd,
                              sprmPIstdPermute,
                              sprmPIncLvl,
                              sprmPJc,
                              sprmPFSideBySide,
                              sprmPFKeep,
                              sprmPFKeepFollow,
                              sprmPFPageBreakBefore,
                              sprmPBrcl,
                              sprmPBrcp,
                              sprmPAnld,
                              sprmPNLvlAnmFake,
                              sprmPFNoLineNumb,
                              sprmPChgTabsPapx,
                              sprmPDxaRight,
                              sprmPDxaLeft,
                              sprmPNest,
                              sprmPDxaLeft1,
                              sprmPDyaLine,
                              sprmPDyaBefore,
                              sprmPDyaAfter,
                              sprmPChgTabs,
                              sprmPFInTable,
                              sprmPFTtp,
                              sprmPDxaAbs,
                              sprmPDyaAbs,
                              sprmPDxaWidth,
                              sprmPPc,
                              sprmPBrcTop10,
                              sprmPBrcLeft10,
                              sprmPBrcBottom10,
                              sprmPBrcRight10,
                              sprmPBrcBetween10,
                              sprmPBrcBar10,
                              sprmPDxaFromText10,
                              sprmPWr,
                              sprmPBrcTop,
                              sprmPBrcLeft,
                              sprmPBrcBottom,
                              sprmPBrcRight,
                              sprmPBrcBetween,
                              sprmPBrcBar,
                              sprmPFNoAutoHyph,
                              sprmPWHeightAbs,
                              sprmPDcs,
                              sprmPShd80,
                              sprmPDyaFromText,
                              sprmPDxaFromText,
                              sprmPFLocked,
                              sprmPFWidowControl,
                              sprmPRuler,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmCFRMarkDel,
                              sprmCFRMark,
                              sprmCFFldVanish,
                              sprmCPicLocation,
                              sprmCIbstRMark,
                              sprmCDttmRMark,
                              sprmCFData,
                              sprmCIdslRMark,
                              sprmCChs,
                              sprmCSymbol,
                              sprmCFOle2,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmCIstd,
                              sprmCIstdPermute,
                              sprmCDefault,
                              sprmCPlain,
                              sprmNoop,
                              sprmCFBold,
                              sprmCFItalic,
                              sprmCFStrike,
                              sprmCFOutline,
                              sprmCFShadow,
                              sprmCFSmallCaps,
                              sprmCFCaps,
                              sprmCFVanish,
                              sprmCFtcDefault,
                              sprmCKul,
                              sprmCSizePos,
                              sprmCDxaSpace,
                              sprmCLid,
                              sprmCIco,
                              sprmCHps,
                              sprmCHpsInc,
                              sprmCHpsPos,
                              sprmCHpsPosAdj,
                              sprmCMajority,
                              sprmCIss,
                              sprmCHpsNew50,
                              sprmCHpsInc1,
                              sprmCHpsKern,
                              sprmCMajority50,
                              sprmCHpsMul,
                              sprmCYsri,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmCFSpec,
                              sprmCFObj,
                              sprmPicBrcl,
                              sprmPicScale,
                              sprmPicBrcTop,
                              sprmPicBrcLeft,
                              sprmPicBrcBottom,
                              sprmPicBrcRight,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmScnsPgn,
                              sprmSiHeadingPgn,
                              sprmSOlstAnm,
                              sprmNoop,
                              sprmNoop,
                              sprmSDxaColWidth,
                              sprmSDxaColSpacing,
                              sprmSFEvenlySpaced,
                              sprmSFProtected,
                              sprmSDmBinFirst,
                              sprmSDmBinOther,
                              sprmSBkc,
                              sprmSFTitlePage,
                              sprmSCcolumns,
                              sprmSDxaColumns,
                              sprmSFAutoPgn,
                              sprmSNfcPgn,
                              sprmSDyaPgn,
                              sprmSDxaPgn,
                              sprmSFPgnRestart,
                              sprmSFEndnote,
                              sprmSLnc,
                              sprmSGprfIhdt,
                              sprmSNLnnMod,
                              sprmSDxaLnn,
                              sprmSDyaHdrTop,
                              sprmSDyaHdrBottom,
                              sprmSLBetween,
                              sprmSVjc,
                              sprmSLnnMin,
                              sprmSPgnStart,
                              sprmSBOrientation,
                              sprmSBCustomize,
                              sprmSXaPage,
                              sprmSYaPage,
                              sprmSDxaLeft,
                              sprmSDxaRight,
                              sprmSDyaTop,
                              sprmSDyaBottom,
                              sprmSDzaGutter,
                              sprmSDmPaperReq,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmNoop,
                              sprmTJc,
                              sprmTDxaLeft,
                              sprmTDxaGapHalf,
                              sprmTFCantSplit,
                              sprmTTableHeader,
                              sprmTTableBorders80,
                              sprmTDefTable10,
                              sprmTDyaRowHeight,
                              sprmTDefTable,
                              sprmTDefTableShd80,
                              sprmTTlp,
                              sprmTSetBrc80,
                              sprmTInsert,
                              sprmTDelete,
                              sprmTDxaCol,
                              sprmTMerge,
                              sprmTSplit,
                              sprmTSetBrc10,
                              sprmTSetShd};

    U16 s;
    if (sprm > 200)
        s = sprmNoop;
    else
        s = lut[sprm];
    if (s == sprmNoop)
        wvlog << "Warning: Got a Word 6 " << static_cast<int>(sprm) << " and return a noop!" << Qt::endl;
    return s;
}

} // namespace SPRM

ParagraphProperties *initPAPFromStyle(const U8 *exceptions, const StyleSheet *styleSheet, OLEStreamReader *dataStream, WordVersion version)
{
    ParagraphProperties *properties = nullptr;
    if (exceptions == nullptr) {
        if (!styleSheet) {
            wvlog << "Warning: Couldn't read from the stylesheet." << Qt::endl;
            return new ParagraphProperties();
        }
        const Style *normal = styleSheet->styleByID(0); // stiNormal == 0x0000
        if (normal) {
            properties = new ParagraphProperties(normal->paragraphProperties());
        } else {
            properties = new ParagraphProperties();
        }
    } else {
        int cb = static_cast<int>(*exceptions++) << 1; // Count of words (x2) -> count of bytes
        if (cb == 0) { // odd PAPX -> skip the padding byte
            cb = static_cast<int>(*exceptions++) << 1;
            cb -= 2;
        } else {
            cb -= version == Word8 ? 3 : 2; // Don't ask me, why Word 6/7 only needs -2 bytes
        }

        U16 tmpIstd = readU16(exceptions);
        exceptions += 2;

        const Style *style = nullptr;
        if (styleSheet) {
            style = styleSheet->styleByIndex(tmpIstd);
            if (style) {
                properties = new ParagraphProperties(style->paragraphProperties());
            } else {
                wvlog << "Warning: Couldn't read from the style, just applying the PAPX." << Qt::endl;
                properties = new ParagraphProperties();
            }
        } else {
            wvlog << "Warning: Couldn't read from the stylesheet, just applying the PAPX." << Qt::endl;
            properties = new ParagraphProperties();
        }

        properties->pap().istd = tmpIstd;

        cb = cb < 0 ? 0 : cb; // safety :-}
        // Note: The caller also has to override the PHE from this
        // PAP with the PHE stored in the BX
        properties->pap().apply(exceptions, cb, style, styleSheet, dataStream, version);
    }
    return properties;
}

Word97::TAP *initTAP(const U8 *exceptions, OLEStreamReader *dataStream, WordVersion version)
{
    Word97::TAP *tap = new Word97::TAP;

    if (exceptions == nullptr)
        return tap;

    int cb = static_cast<int>(*exceptions++) << 1; // Count of words (x2) -> count of bytes
    if (cb == 0) { // odd PAPX -> skip the padding byte
        cb = static_cast<int>(*exceptions++) << 1;
        cb -= 2;
    } else
        cb -= 3;

    exceptions += 2; // skip the istd
    cb = cb < 0 ? 0 : cb; // safety :-}
    tap->apply(exceptions, cb, nullptr, nullptr, dataStream, version); // we don't need a style(sheet), do we?

    return tap;
}

// Apply a PAP grpprl of a given size ("count" bytes long)
void PAP::apply(const U8 *grpprl, U16 count, const Style *style, const StyleSheet *styleSheet, OLEStreamReader *dataStream, WordVersion version)
{
    // A PAP grpprl might contain TAP sprms, we just skip them
    SPRM::apply<PAP>(this, &PAP::applyPAPSPRM, grpprl, count, style, styleSheet, dataStream, version);
}

U32 icoToCOLORREF(U16 ico)
{
    // TODO: Do not place the fAuto byte in front!  The MS-ODRAW
    // OfficeArtCOLORREF is an equivalent and has the byte properly at the
    // end.  Oooo, it's confusing ...

    switch (ico) {
    case 0: // default and we choose black as most paper is white
        return 0xFF000000;
    case 1: // black
        return 0x00000000;
    case 2: // blue
        return 0x000000FF;
    case 3: // cyan
        return 0x0000FFFF;
    case 4: // green
        return 0x00008000;
    case 5: // magenta
        return 0x00FF00FF;
    case 6: // red
        return 0x00FF0000;
    case 7: // yellow
        return 0x00FFFF00;
    case 8: // white
        return 0x00FFFFFF;
    case 9: // dark blue
        return 0x0000008B;
    case 10: // dark cyan
        return 0x00008B8B;
    case 11: // dark green
        return 0x00006400;
    case 12: // dark magenta
        return 0x008B008B;
    case 13: // dark red
        return 0x008B0000;
    case 14: // dark yellow
        return 0x00808000;
    case 15: // dark gray
        return 0x00A9A9A9;
    case 16: // light gray
        return 0x00D3D3D3;

    default:
        return 0x000000;
    }
}

// Helper methods for the more complex sprms
namespace
{
/**
 * Adds the tabs of the sprmPChgTabs* sprms.  Pass a pointer to the
 * itbdAddMax and the vector.
 *
 * @return the number of tabs added
 */
typedef std::vector<Word97::TabDescriptor> TabDescVector;
U8 addTabs(const U8 *ptr, TabDescVector &rgdxaTab)
{
    //         wvlog << "Before adding the tabs: " << (int)rgdxaTab.size() << Qt::endl;
    // Remember where the end was
    const TabDescVector::size_type oldSize = rgdxaTab.size();
    // Now append the new ones, we'll then sort the vector using
    // inplace_merge
    const U8 itbdAddMax = *ptr++;
    // wvlog << "                           itbdAddMax=" << (int)itbdAddMax << Qt::endl;
    for (U8 i = 0; i < itbdAddMax; ++i) {
        // #### We should probably add a proper constructor to
        // #### TabDescriptor (Werner)
        TabDescriptor descr;
        descr.dxaTab = readS16(ptr + sizeof(S16) * i);
        //             wvlog << "                           dxaPos=" << descr.dxaTab << Qt::endl;
        descr.tbd = TBD(readU8(ptr + sizeof(S16) * itbdAddMax + i));
        rgdxaTab.push_back(descr);
    }
    if (oldSize != 0 && itbdAddMax) {
        TabDescVector::iterator middle = rgdxaTab.begin();
        middle += oldSize;
        std::inplace_merge(rgdxaTab.begin(), middle, rgdxaTab.end());
    }
    TabDescVector::iterator uend = std::unique(rgdxaTab.begin(), rgdxaTab.end());
    if (uend != rgdxaTab.end()) {
        rgdxaTab.erase(uend, rgdxaTab.end());
    }
    //         wvlog << "After applying sprmPChgTabs(Papx) : " << (int)rgdxaTab.size() << Qt::endl;
    return itbdAddMax;
}

// A zone where tabstops are forbidden, needed for sprmPChgTabs
struct Zone {
    Zone(const U8 *ptr, U8 index, U8 itbdDelMax)
    {
        m_center = readS16(ptr + index * sizeof(S16));
        // A negative value doesn't make sense here, right? Hmmm
        m_plusMinus = std::abs((int)(readS16(ptr + itbdDelMax * sizeof(S16) + index * sizeof(S16))));
    }

    bool contains(S16 position) const
    {
        return m_center - m_plusMinus <= position && m_center + m_plusMinus >= position;
    }

    S16 m_center;
    S16 m_plusMinus;
};

U16 getSPRM(const U8 **ptr, WordVersion version, U16 &sprmLength)
{
    U16 sprm;
    if (version == Word8) {
        sprm = readU16(*ptr);
        (*ptr) += 2;
        sprmLength = SPRM::determineParameterLength(sprm, *ptr, version) + 2;
    } else {
        sprm = **ptr;
        ++(*ptr);
        sprmLength = SPRM::determineParameterLength(sprm, *ptr, version) + 1;
        sprm = SPRM::word6toWord8(sprm); // "fix" it for the Word 8 switch statements below
    }
    return sprm;
}

void readBRC(BRC &brc, const U8 *ptr, WordVersion version)
{
    if (version == Word8)
        brc.readPtr(ptr);
    else
        brc = toWord97(Word95::BRC(ptr));
}
} // anonymous namespace

// Returns -1 if this wasn't a PAP sprm and it returns the length
// of the applied sprm if it was successful
S16 PAP::applyPAPSPRM(const U8 *ptr, const Style *style, const StyleSheet *styleSheet, OLEStreamReader *dataStream, WordVersion version)
{
    U16 sprmLength;
    const U16 sprm(getSPRM(&ptr, version, sprmLength));
#ifdef WV2_DEBUG_SPRMS
    wvlog << "got a sprm: 0x" << hex << sprm << dec << Qt::endl;
#endif

    // Is it a PAP sprm?
    if (((sprm & 0x1C00) >> 10) != 1) {
#ifdef WV2_DEBUG_SPRMS
        wvlog << "Warning: You're trying to apply a non PAP sprm to a PAP. Not necessarily bad." << Qt::endl;
#endif
        return -1; // tell the caller to try with something else (e.g. applying a TAP)...
    }
    // which one? ;)
    switch (sprm) {
    case SPRM::sprmNoop:
        wvlog << "Huh? Found a sprmNoop..." << Qt::endl;
        break;
    case SPRM::sprmPIstd:
        istd = readU16(ptr);
        break;
    case SPRM::sprmPIstdPermute: {
        const U8 *myPtr = ptr + 3; // cch, fLongg, fSpare
        const U16 istdFirst = readU16(myPtr);
        myPtr += 2;
        const U16 istdLast = readU16(myPtr);
        myPtr += 2;
        if (istd > istdFirst && istd <= istdLast)
            istd = myPtr[istd - istdFirst];
        break;
    }
    case SPRM::sprmPIncLvl:
        // Applies only to list styles, note: SPEC defect, there's no stc anymore
        if (istd >= 1 && istd <= 9) {
            istd += static_cast<S8>(*ptr);
            lvl += static_cast<S8>(*ptr);
        }
        break;
    case SPRM::sprmPJc:
    case SPRM::sprmPJcFE: // asian version, according to OOo (fall-through intended)
        jc = *ptr;
        break;
    case SPRM::sprmPFSideBySide:
        fSideBySide = *ptr == 1;
        break;
    case SPRM::sprmPFKeep:
        fKeep = *ptr == 1;
        break;
    case SPRM::sprmPFKeepFollow:
        fKeepFollow = *ptr == 1;
        break;
    case SPRM::sprmPFPageBreakBefore:
        fPageBreakBefore = *ptr == 1;
        break;
    case SPRM::sprmPBrcl:
        brcl = *ptr;
        break;
    case SPRM::sprmPBrcp:
        brcp = *ptr;
        break;
    case SPRM::sprmPIlvl:
        ilvl = *ptr;
        break;
    case SPRM::sprmPIlfo:
        ilfo = readS16(ptr);
        break;
    case SPRM::sprmPFNoLineNumb:
        fNoLnn = *ptr == 1;
        break;
    case SPRM::sprmPChgTabsPapx: {
        const U8 *myPtr = ptr;
        U8 cch = *myPtr++;
        U8 itbdDelMax = *myPtr++;
        std::vector<Word97::TabDescriptor>::iterator tabIt = rgdxaTab.begin();
        // wvlog << "Applying sprmPChgTabsPapx. itbdDelMax=" << (int)itbdDelMax << Qt::endl;
        //  First the 'to be deleted' array
        for (U8 i = 0; i < itbdDelMax; ++i) {
            TabDescriptor testDescr;
            testDescr.dxaTab = readS16(myPtr);
            myPtr += 2;
            // Look for this one, starting at last position (they are sorted)
            tabIt = std::find(tabIt, rgdxaTab.end(), testDescr);
            if (tabIt != rgdxaTab.end()) {
                tabIt = rgdxaTab.erase(tabIt);
            }
        }
        U8 itbdAddMax = addTabs(myPtr, rgdxaTab);
        itbdMac = rgdxaTab.size();

        if (cch != 1 + 2 * itbdDelMax + 1 + 3 * itbdAddMax) {
            wvlog << "Offset problem in sprmPChgTabsPapx. cch=" << static_cast<int>(cch) << "data size=" << 1 + 2 * itbdDelMax + 1 + 3 * itbdAddMax << Qt::endl;
        }
#ifdef WV2_DEBUG_SPRMS
        wvlog << "After applying sprmPChgTabsPapx : " << (int)rgdxaTab.size() << Qt::endl;
        for (uint i = 0; i < rgdxaTab.size(); i++) {
            wvlog << "rgdxaTab[" << i << "].dxaTab" << rgdxaTab[i].dxaTab;
        }
#endif
        break;
    }
    case SPRM::sprmPDxaRight:
    case SPRM::sprmPDxaRightFE: // asian version, according to OOo (fall-through intended)
        dxaRight = readS16(ptr);
        break;
    case SPRM::sprmPDxaLeft:
    case SPRM::sprmPDxaLeftFE: // asian version, according to OOo (fall-through intended)
        dxaLeft = readS16(ptr);
#ifdef WV2_DEBUG_SPRMS
        wvlog << "dxaLeft:" << dxaLeft;
#endif
        break;
    case SPRM::sprmPNest:
        dxaLeft += readS16(ptr);
        dxaLeft = dxaLeft < 0 ? 0 : dxaLeft;
        break;
    case SPRM::sprmPDxaLeft1:
    case SPRM::sprmPDxaLeft1FE: // asian version, according to OOo (fall-through intended)
        dxaLeft1 = readS16(ptr);
#ifdef WV2_DEBUG_SPRMS
        wvlog << "dxaLeft1:" << dxaLeft1;
#endif
        break;
    case SPRM::sprmPDyaLine:
        lspd.dyaLine = readS16(ptr);
        lspd.fMultLinespace = readS16(ptr + 2);
        break;
    case SPRM::sprmPDyaBefore:
        dyaBefore = readU16(ptr);
        break;
    case SPRM::sprmPDyaAfter:
        dyaAfter = readU16(ptr);
        break;
    case SPRM::sprmPChgTabs: {
        const U8 *myPtr = ptr;
        const U8 cch = *myPtr++;
        const U8 itbdDelMax = *myPtr++;
        // Remove the tabs within the deletion zones
        std::vector<TabDescriptor>::iterator newEnd = rgdxaTab.end();
        for (U8 i = 0; i < itbdDelMax; ++i) {
            auto inZone = [=](const TabDescriptor &tab) {
                return Zone(myPtr, i, itbdDelMax).contains(tab.dxaTab);
            };
            newEnd = std::remove_if(rgdxaTab.begin(), newEnd, inZone);
        }
        rgdxaTab.erase(newEnd, rgdxaTab.end()); // really get rid of them
        myPtr += itbdDelMax * 4;

        U8 itbdAddMax = addTabs(myPtr, rgdxaTab);
        itbdMac = rgdxaTab.size();

        if (cch != 255 && cch != 1 + 4 * itbdDelMax + 1 + 3 * itbdAddMax) {
            wvlog << "Offset problem in sprmPChgTabs. cch=" << static_cast<int>(cch) << "data size=" << 1 + 4 * itbdDelMax + 1 + 3 * itbdAddMax << Qt::endl;
        }
#ifdef WV2_DEBUG_SPRMS
        wvlog << "After applying sprmPChgTabs : " << (int)rgdxaTab.size() << Qt::endl;
        for (uint i = 0; i < rgdxaTab.size(); i++) {
            wvlog << "rgdxaTab[" << i << "].dxaTab" << rgdxaTab[i].dxaTab;
        }
#endif
        break;
    }
    case SPRM::sprmPDxaAbs:
        dxaAbs = readS16(ptr);
        break;
    case SPRM::sprmPDyaAbs:
        dyaAbs = readS16(ptr);
        break;
    case SPRM::sprmPDxaWidth:
        dxaWidth = readS16(ptr);
        break;
    case SPRM::sprmPPc: {
        pcVert = (*ptr & 0x30) >> 4;
        pcHorz = (*ptr & 0xC0) >> 6;
        break;
    }
    case SPRM::sprmPBrcTop10:
        wvlog << "Warning: sprmPBrcTop10 doesn't make sense for Word 8" << Qt::endl;
        break;
    case SPRM::sprmPBrcLeft10:
        wvlog << "Warning: sprmPBrcLeft10 doesn't make sense for Word 8" << Qt::endl;
        break;
    case SPRM::sprmPBrcBottom10:
        wvlog << "Warning: sprmPBrcBottom10 doesn't make sense for Word 8" << Qt::endl;
        break;
    case SPRM::sprmPBrcRight10:
        wvlog << "Warning: sprmPBrcRight10 doesn't make sense for Word 8" << Qt::endl;
        break;
    case SPRM::sprmPBrcBetween10:
        wvlog << "Warning: sprmPBrcBetween10 doesn't make sense for Word 8" << Qt::endl;
        break;
    case SPRM::sprmPBrcBar10:
        wvlog << "Warning: sprmPBrcBar10 doesn't make sense for Word 8" << Qt::endl;
        break;
    case SPRM::sprmPDxaFromText10:
        wvlog << "Warning: sprmPDxaFromText10 doesn't make sense for Word 8" << Qt::endl;
        break;
    case SPRM::sprmPWr:
        wr = *ptr;
        break;
    case SPRM::sprmPBrcTop:
        readBRC(brcTop, ptr, version);
        break;
    case SPRM::sprmPBrcLeft:
        readBRC(brcLeft, ptr, version);
        break;
    case SPRM::sprmPBrcBottom:
        readBRC(brcBottom, ptr, version);
        break;
    case SPRM::sprmPBrcRight:
        readBRC(brcRight, ptr, version);
        break;
    case SPRM::sprmPBrcBetween:
        readBRC(brcBetween, ptr, version);
        break;
    case SPRM::sprmPBrcBar:
        readBRC(brcBar, ptr, version);
        break;
    case SPRM::sprmPFNoAutoHyph:
        fNoAutoHyph = *ptr == 1;
        break;
    case SPRM::sprmPWHeightAbs: {
        U16 heightAbsTmp = readU16(ptr);
        dyaHeight = (heightAbsTmp & 0xfffe);
        break;
    }
    case SPRM::sprmPDcs:
        dcs.readPtr(ptr);
        break;
    case SPRM::sprmPShd80:
        shd.readPtr(ptr);
        break;
    case SPRM::sprmPShd:
        shd.readSHDOperandPtr(ptr); // and read the SHDOperand structure into SHD
        break;
    case SPRM::sprmPDyaFromText:
        dyaFromText = readS16(ptr);
        break;
    case SPRM::sprmPDxaFromText:
        dxaFromText = readS16(ptr);
        break;
    case SPRM::sprmPFLocked:
        fLocked = *ptr == 1;
        break;
    case SPRM::sprmPFWidowControl:
        fWidowControl = *ptr == 1;
        break;
    case SPRM::sprmPRuler:
        wvlog << "Warning: sprmPRuler not implemented" << Qt::endl;
        break;
    case SPRM::sprmPFKinsoku:
        fKinsoku = *ptr == 1;
        break;
    case SPRM::sprmPFWordWrap:
        fWordWrap = *ptr == 1;
        break;
    case SPRM::sprmPFOverflowPunct:
        fOverflowPunct = *ptr == 1;
        break;
    case SPRM::sprmPFTopLinePunct:
        fTopLinePunct = *ptr == 1;
        break;
    case SPRM::sprmPFAutoSpaceDE:
        fAutoSpaceDE = *ptr == 1;
        break;
    case SPRM::sprmPFAutoSpaceDN:
        fAutoSpaceDN = *ptr == 1;
        break;
    case SPRM::sprmPWAlignFont:
        wAlignFont = readU16(ptr);
        break;
    case SPRM::sprmPFrameTextFlow:
        wvlog << "Warning: sprmPFrameTextFlow not implemented" << Qt::endl;
        break;
    case SPRM::sprmPISnapBaseLine:
        wvlog << "Warning: sprmPISnapBaseLine is obsolete" << Qt::endl;
        break;
    case SPRM::sprmPAnld:
        if (version == Word8)
            anld.readPtr(ptr + 1); // variable length, skip length byte
        else
            anld = toWord97(Word95::ANLD(ptr + 1));
        break;
    case SPRM::sprmPPropRMark:
        fPropRMark = *(ptr + 1);
        ibstPropRMark = readS16(ptr + 2);
        dttmPropRMark.readPtr(ptr + 4);
        break;
    case SPRM::sprmPOutLvl:
        lvl = readS8(ptr);
        break;
    case SPRM::sprmPFBiDi:
        fBiDi = readS8(ptr);
        break;
    case SPRM::sprmPFNumRMIns:
        fNumRMIns = *ptr == 1;
        break;
    case SPRM::sprmPCrLf:
        fCrLf = *ptr;
        break;
    case SPRM::sprmPNumRM:
        numrm.readPtr(ptr + 1);
        break;
    case SPRM::sprmPHugePapx:
    case SPRM::sprmPHugePapx2: {
        if (dataStream) {
            dataStream->push();
            dataStream->seek(readU32(ptr), WV2_SEEK_SET);
            const U16 count(dataStream->readU16());
            U8 *grpprl = new U8[count];
            dataStream->read(grpprl, count);
            dataStream->pop();

            apply(grpprl, count, style, styleSheet, dataStream, version);
            delete[] grpprl;
        } else
            wvlog << "Error: sprmPHugePapx found, but no data stream!" << Qt::endl;
        break;
    }
    case SPRM::sprmPFUsePgsuSettings:
        fUsePgsuSettings = *ptr == 1;
        break;
    case SPRM::sprmPFAdjustRight:
        fAdjustRight = *ptr == 1;
        break;
    case SPRM::sprmPNLvlAnmFake:
        nLvlAnm = *ptr;
        break;
    case SPRM::sprmPFDyaBeforeAuto:
        dyaBeforeAuto = *ptr == 1;
        break;
    case SPRM::sprmPFDyaAfterAuto:
        dyaAfterAuto = *ptr == 1;
        break;
    // START - table related SPRMs
    case SPRM::sprmPFInTable:
        fInTable = *ptr == 1;
        break;
    case SPRM::sprmPFTtp:
        fTtp = *ptr == 1;
        break;
    case SPRM::sprmPItap:
        itap = readU32(ptr);
        break;
    case SPRM::sprmPDtap:
        dtap = readS32(ptr);
        break;
    case SPRM::sprmPFInnerTableCell:
        fInnerTableCell = *ptr == 1;
        break;
    case SPRM::sprmPFInnerTtp:
        fInnerTtp = *ptr == 1;
        break;
    // END - table related SPRMs
    case SPRM::sprmPUndocumented1:
    case SPRM::sprmPUndocumented2:
    case SPRM::sprmPUndocumented3:
    case SPRM::sprmPUndocumented4:
        break;
    default:
        wvlog << "Huh? None of the defined sprms matches 0x" << Qt::hex << sprm << Qt::dec << "... trying to skip anyway" << Qt::endl;
        break;
    }
    return static_cast<S16>(sprmLength); // length of the SPRM
}

// Apply a CHP grpprl of a given size ("count" bytes long)
void CHP::apply(const U8 *grpprl, U16 count, const Style *paragraphStyle, const StyleSheet *styleSheet, OLEStreamReader *dataStream, WordVersion version)
{
    // There should be only CHP sprms in the grpprl we get
    SPRM::apply<CHP>(this, &CHP::applyCHPSPRM, grpprl, count, paragraphStyle, styleSheet, dataStream, version);
}

void CHP::applyExceptions(const U8 *exceptions, const Style *paragraphStyle, const StyleSheet *styleSheet, OLEStreamReader *dataStream, WordVersion version)
{
    if (exceptions == nullptr)
        return;
    U8 cb = *exceptions;
    ++exceptions;
    apply(exceptions, cb, paragraphStyle, styleSheet, dataStream, version);
}

// Helper functions for more complex sprms

// Returns -1 if this wasn't a CHP sprm and it returns the length
// of the applied sprm if it was successful
S16 CHP::applyCHPSPRM(const U8 *ptr, const Style *paragraphStyle, const StyleSheet *styleSheet, OLEStreamReader *dataStream, WordVersion version)
{
    U16 sprmLength;
    const U16 sprm(getSPRM(&ptr, version, sprmLength));
#ifdef WV2_DEBUG_SPRMS
    wvlog << "got a sprm: 0x" << hex << sprm << dec << Qt::endl;
#endif

    // Is it a CHP sprm?
    if (((sprm & 0x1C00) >> 10) != 2) {
#ifdef WV2_DEBUG_SPRMS
        wvlog << "Warning: You're trying to apply a non CHP sprm to a CHP. Not necessarily bad." << Qt::endl;
#endif
        return -1;
    }
    // which one? ;)
    switch (sprm) {
    case SPRM::sprmNoop:
        wvlog << "Huh? Found a sprmNoop..." << Qt::endl;
        break;
    case SPRM::sprmCFRMarkDel:
        fRMarkDel = *ptr == 1;
        break;
    case SPRM::sprmCFRMark:
        fRMark = *ptr == 1;
        break;
    case SPRM::sprmCFFldVanish:
        fFldVanish = *ptr == 1;
        break;
    case SPRM::sprmCPicLocation:
        fSpec = 1;
        fcPic_fcObj_lTagObj = readS32(ptr + (version == Word8 ? 0 : 1));
        break;
    case SPRM::sprmCIbstRMark:
        ibstRMark = readS16(ptr);
        break;
    case SPRM::sprmCDttmRMark:
        dttmRMark.readPtr(ptr);
        break;
    case SPRM::sprmCFData:
        fData = *ptr == 1;
        break;
    case SPRM::sprmCIdslRMark:
        idslRMReason = readS16(ptr);
        break;
    case SPRM::sprmCChs:
        fChsDiff = *ptr == 1;
        chse = readU16(ptr + 1);
        break;
    case SPRM::sprmCSymbol:
        if (version == Word8) {
            ftcSym = readS16(ptr);
            xchSym = readS16(ptr + 2);
        } else {
            // First the length byte...
            ftcSym = readS16(ptr + 1);
            xchSym = *(ptr + 3);
        }
#ifdef WV2_DEBUG_SPRMS
        wvlog << "sprmCSymbol: ftcSym=" << ftcSym << " xchSym=" << xchSym << Qt::endl;
#endif
        fSpec = 1;
        break;
    case SPRM::sprmCFOle2:
        fOle2 = *ptr == 1;
        break;
    case SPRM::sprmCIdCharType:
        wvlog << "Warning: sprmCIdCharType doesn't make sense for Word 8" << Qt::endl;
        break;
    case SPRM::sprmCHighlight:
        icoHighlight = *ptr;
        fHighlight = icoHighlight != 0 ? 1 : 0;
        break;
    case SPRM::sprmCObjLocation:
        fcPic_fcObj_lTagObj = readU32(ptr);
        break;
    case SPRM::sprmCFFtcAsciSymb:
        fFtcAsciSym = *ptr == 1;
        break;
    case SPRM::sprmCIstd: {
        wvlog << "######################## old character style = " << istd << Qt::endl;
        istd = readS16(ptr);
        if (styleSheet) {
            wvlog << "Trying to change the character style to " << istd << Qt::endl;
            const Style *style = styleSheet->styleByIndex(istd);
            if (style && style->type() == sgcChp) {
                wvlog << "got a character style!" << Qt::endl;
                const UPECHPX &upechpx(style->upechpx());
                apply(upechpx.grpprl, upechpx.cb, paragraphStyle, styleSheet, dataStream, version);
            } else {
                wvlog << "Warning: Couldn't find the character style with istd " << istd << Qt::endl;
            }
        } else {
            wvlog << "Warning: Tried to change the character style, but the stylesheet was 0" << Qt::endl;
        }
        break;
    }
    case SPRM::sprmCIstdPermute: {
        const U8 *myPtr = ptr + 3; // cch, fLongg, fSpare
        const U16 istdFirst = readU16(myPtr);
        myPtr += 2;
        const U16 istdLast = readU16(myPtr);
        myPtr += 2;
        if (istd > istdFirst && istd <= istdLast) {
            istd = myPtr[istd - istdFirst];
        }
        break;
    }
    case SPRM::sprmCDefault:
        fBold = false;
        fItalic = false;
        fOutline = false;
        fStrike = false;
        fShadow = false;
        fSmallCaps = false;
        fCaps = false;
        fVanish = false;
        kul = 0;
        cv = 0;
        break;
    case SPRM::sprmCPlain: {
        bool fSpecBackup = fSpec;
        if (paragraphStyle) {
            *this = paragraphStyle->chp();
        }
        fSpec = fSpecBackup;
        break;
    }
    case SPRM::sprmCKcd:
        kcd = *ptr;
        break;
    case SPRM::sprmCFBold:
#ifdef WV2_DEBUG_SPRMS
        wvlog << "sprmCFBold operand: 0x" << hex << *ptr << "| istd: 0x" << hex << istd << "| paragraphStyle:" << paragraphStyle;
#endif
        if (*ptr < 128) {
            fBold = *ptr == 1;
        } else if (*ptr == 128 && paragraphStyle) {
            fBold = paragraphStyle->chp().fBold;
        } else if (*ptr == 129 && paragraphStyle) {
            fBold = !(paragraphStyle->chp().fBold);
        } else {
            wvlog << "Warning: sprmCFBold couldn't find a style" << Qt::endl;
            fBold = !fBold;
        }
        break;
    case SPRM::sprmCFItalic:
        if (*ptr < 128) {
            fItalic = *ptr == 1;
        } else if (*ptr == 128 && paragraphStyle) {
            fItalic = paragraphStyle->chp().fItalic;
        } else if (*ptr == 129 && paragraphStyle) {
            fItalic = !(paragraphStyle->chp().fItalic);
        } else {
            wvlog << "Warning: sprmCFItalic couldn't find a style" << Qt::endl;
            fItalic = !fItalic;
        }
        break;
    case SPRM::sprmCFStrike:
#ifdef WV2_DEBUG_SPRMS
        wvlog << "sprmCFStrike -- fStrike = " << static_cast<int>(fStrike) << " *ptr = " << static_cast<int>(*ptr) << Qt::endl;
#endif
        if (*ptr < 128) {
            fStrike = *ptr == 1;
        } else if (*ptr == 128 && paragraphStyle) {
            fStrike = paragraphStyle->chp().fStrike;
        } else if (*ptr == 129 && paragraphStyle) {
            fStrike = !(paragraphStyle->chp().fStrike);
        } else {
            wvlog << "Warning: sprmCFStrike couldn't find a style" << Qt::endl;
            fStrike = !fStrike;
        }
#ifdef WV2_DEBUG_SPRMS
        wvlog << "sprmCFStrike -- fStrike (changed) = " << static_cast<int>(fStrike) << Qt::endl;
#endif
        break;
    case SPRM::sprmCFOutline:
        if (*ptr < 128) {
            fOutline = *ptr == 1;
        } else if (*ptr == 128 && paragraphStyle) {
            fOutline = paragraphStyle->chp().fOutline;
        } else if (*ptr == 129 && paragraphStyle) {
            fOutline = !(paragraphStyle->chp().fOutline);
        } else {
            wvlog << "Warning: sprmCFOutline couldn't find a style" << Qt::endl;
            fOutline = !fOutline;
        }
        break;
    case SPRM::sprmCFShadow:
        if (*ptr < 128) {
            fShadow = *ptr == 1;
        } else if (*ptr == 128 && paragraphStyle) {
            fShadow = paragraphStyle->chp().fShadow;
        } else if (*ptr == 129 && paragraphStyle) {
            fShadow = !(paragraphStyle->chp().fShadow);
        } else {
            wvlog << "Warning: sprmCFShadow couldn't find a style" << Qt::endl;
            fShadow = !fShadow;
        }
        break;
    case SPRM::sprmCFSmallCaps:
        if (*ptr < 128) {
            fSmallCaps = *ptr == 1;
        } else if (*ptr == 128 && paragraphStyle) {
            fSmallCaps = paragraphStyle->chp().fSmallCaps;
        } else if (*ptr == 129 && paragraphStyle) {
            fSmallCaps = !(paragraphStyle->chp().fSmallCaps);
        } else {
            wvlog << "Warning: sprmCFSmallCaps couldn't find a style" << Qt::endl;
            fSmallCaps = !fSmallCaps;
        }
        break;
    case SPRM::sprmCFCaps:
        if (*ptr < 128) {
            fCaps = *ptr == 1;
        } else if (*ptr == 128 && paragraphStyle) {
            fCaps = paragraphStyle->chp().fCaps;
        } else if (*ptr == 129 && paragraphStyle) {
            fCaps = !(paragraphStyle->chp().fCaps);
        } else {
            wvlog << "Warning: sprmCFCaps couldn't find a style" << Qt::endl;
            fCaps = !fCaps;
        }
        break;
    case SPRM::sprmCFVanish:
        if (*ptr < 128) {
            fVanish = *ptr == 1;
        } else if (*ptr == 128 && paragraphStyle) {
            fVanish = paragraphStyle->chp().fVanish;
        } else if (*ptr == 129 && paragraphStyle) {
            fVanish = !(paragraphStyle->chp().fVanish);
        } else {
            wvlog << "Warning: sprmCFVanish couldn't find a style" << Qt::endl;
            fVanish = !fVanish;
        }
        break;
    case SPRM::sprmCFtcDefault:
        // We are abusing this SPRM for Word 6 purposes (sprmCFtc, 93)
        // wvlog << "Error: sprmCFtcDefault only used internally in MS Word" << Qt::endl;
        ftcAscii = ftcFE = ftcOther = ftc = readS16(ptr);
        break;
    case SPRM::sprmCKul:
        kul = *ptr;
        break;
    case SPRM::sprmCSizePos:
        // The hps sprms would be quite hard to implement in a sane way
        wvlog << "Warning: sprmCSizePos not implemented" << Qt::endl;
        break;
    case SPRM::sprmCDxaSpace:
        dxaSpace = readS16(ptr);
        break;
    case SPRM::sprmCLid:
        // We are abusing this SPRM for Word 6 purposes (sprmCLid, 97)
        lidDefault = lidFE = lid = readU16(ptr);
        // wvlog << "Error: sprmCLid only used internally in MS Word" << Qt::endl;
        break;
    case SPRM::sprmCIco: {
        U16 ico = *ptr;
        cv = Word97::icoToCOLORREF(ico);
        break;
    }
    case SPRM::sprmCCv: {
        U8 r, g, b, k;

        r = readU8(ptr);
        ptr += sizeof(U8);
        g = readU8(ptr);
        ptr += sizeof(U8);
        b = readU8(ptr);
        ptr += sizeof(U8);
        k = readU8(ptr);
        ptr += sizeof(U8);
        cv = (k << 24) | (r << 16) | (g << 8) | (b);
        break;
    }
    case SPRM::sprmCCvUl: {
        U8 r, g, b, k;

        r = readU8(ptr);
        ptr += sizeof(U8);
        g = readU8(ptr);
        ptr += sizeof(U8);
        b = readU8(ptr);
        ptr += sizeof(U8);
        k = readU8(ptr);
        ptr += sizeof(U8);
        cvUl = (k << 24) | (r << 16) | (g << 8) | (b);
        break;
    }
    case SPRM::sprmCHps:
        hps = readU16(ptr);
        break;
    case SPRM::sprmCHpsInc:
        // The hps sprms would be quite hard to implement in a sane way
        wvlog << "Warning: sprmCHpsInc not implemented" << Qt::endl;
        break;
    case SPRM::sprmCHpsPos:
        hpsPos = readS16(ptr);
        break;
    case SPRM::sprmCHpsPosAdj:
        // The hps sprms would be quite hard to implement in a sane way
        wvlog << "Warning: sprmCHpsPosAdj not implemented" << Qt::endl;
        break;
    case SPRM::sprmCMajority:
    case SPRM::sprmCMajority50: // same as sprmCMajority
    {
        CHP tmpChp;
        tmpChp.ftc = 4; // the rest is default, looks a bit strange
        tmpChp.apply(ptr + 1, *ptr, paragraphStyle, styleSheet, dataStream, version);
        if (paragraphStyle) {
            const CHP &pstyle(paragraphStyle->chp());
            if (tmpChp.fBold == fBold)
                fBold = pstyle.fBold;
            if (tmpChp.fItalic == fItalic)
                fItalic = pstyle.fItalic;
            if (tmpChp.fStrike == fStrike)
                fStrike = pstyle.fStrike;
            if (tmpChp.fOutline == fOutline)
                fOutline = pstyle.fOutline;
            if (tmpChp.fShadow == fShadow)
                fShadow = pstyle.fShadow;
            if (tmpChp.fSmallCaps == fSmallCaps)
                fSmallCaps = pstyle.fSmallCaps;
            if (tmpChp.fCaps == fCaps)
                fCaps = pstyle.fCaps;
            if (tmpChp.ftc == ftc)
                ftc = pstyle.ftc;
            if (tmpChp.hps == hps)
                hps = pstyle.hps;
            if (tmpChp.hpsPos == hpsPos)
                hpsPos = pstyle.hpsPos;
            if (tmpChp.kul == kul)
                kul = pstyle.kul;
            if (tmpChp.dxaSpace == dxaSpace) // qpsSpace???
                dxaSpace = pstyle.dxaSpace;
            if (tmpChp.cv == cv)
                cv = pstyle.cv;
        } else
            wvlog << "Warning: sprmCMajority couldn't find a style" << Qt::endl;
        break;
    }
    case SPRM::sprmCIss:
        iss = *ptr;
        break;
    case SPRM::sprmCHpsNew50:
        if (*ptr != 2)
            wvlog << "Warning: sprmCHpsNew50 has a different length than 2" << Qt::endl;
        else
            hps = readU16(ptr + 1);
        break;
    case SPRM::sprmCHpsInc1:
        // The hps sprms would be quite hard to implement in a sane way
        wvlog << "Warning: sprmCHpsInc1 not implemented" << Qt::endl;
        break;
    case SPRM::sprmCHpsKern:
        hpsKern = readU16(ptr);
        break;
    case SPRM::sprmCHpsMul:
        hps = static_cast<int>(static_cast<double>(hps) * (1.0 + static_cast<double>(readS16(ptr)) / 100.0));
        break;
    case SPRM::sprmCYsri:
        // Undocumented, no idea if that's implemented correctly
        ysr = *ptr;
        chYsr = *(ptr + 1);
        break;
    case SPRM::sprmCRgFtc0:
        ftcAscii = readS16(ptr);
        break;
    case SPRM::sprmCRgFtc1:
        ftcFE = readS16(ptr);
        break;
    case SPRM::sprmCRgFtc2:
        ftcOther = readS16(ptr);
        break;
    case SPRM::sprmCCharScale:
        wCharScale = readU16(ptr); // undocumented, but should be okay
        break;
    case SPRM::sprmCFDStrike:
        if (*ptr < 128) {
            fDStrike = *ptr == 1;
        } else if (*ptr == 128 && paragraphStyle) {
            fDStrike = paragraphStyle->chp().fDStrike;
        } else if (*ptr == 129 && paragraphStyle) {
            fDStrike = !(paragraphStyle->chp().fDStrike);
        } else {
            wvlog << "Warning: sprmCFDStrike couldn't find a style" << Qt::endl;
            fDStrike = !fDStrike;
        }
        break;
    case SPRM::sprmCFImprint:
        if (*ptr < 128) {
            fImprint = *ptr == 1;
        } else if (*ptr == 128 && paragraphStyle) {
            fImprint = paragraphStyle->chp().fImprint;
        } else if (*ptr == 129 && paragraphStyle) {
            fImprint = !(paragraphStyle->chp().fImprint);
        } else {
            wvlog << "Warning: sprmCFImprint couldn't find a style" << Qt::endl;
            fImprint = !fImprint;
        }
        break;
    case SPRM::sprmCFSpec:
        if (*ptr < 128) {
            fSpec = *ptr == 1;
        } else if (*ptr == 128 && paragraphStyle) {
            fSpec = paragraphStyle->chp().fSpec;
        } else if (*ptr == 129 && paragraphStyle) {
            fSpec = !(paragraphStyle->chp().fSpec);
        } else {
            wvlog << "Warning: sprmCFSpec couldn't find a style" << Qt::endl;
            fSpec = !fSpec;
        }
        break;
    case SPRM::sprmCFObj:
        fObj = *ptr == 1;
        break;
    case SPRM::sprmCPropRMark:
        if (*ptr != 7) {
            wvlog << "Error: sprmCPropRMark has an unexpected size" << Qt::endl;
        }
        fPropMark = *(ptr + 1) == 1;
        ibstPropRMark = readS16(ptr + 2);
        dttmPropRMark.readPtr(ptr + 4);
        break;
    case SPRM::sprmCFEmboss:
        if (*ptr < 128) {
            fEmboss = *ptr == 1;
        } else if (*ptr == 128 && paragraphStyle) {
            fEmboss = paragraphStyle->chp().fEmboss;
        } else if (*ptr == 129 && paragraphStyle) {
            fEmboss = !(paragraphStyle->chp().fEmboss);
        } else {
            wvlog << "Warning: sprmCFEmboss couldn't find a style" << Qt::endl;
            fEmboss = !fEmboss;
        }
        break;
    case SPRM::sprmCSfxText:
        sfxtText = *ptr;
        break;
    // All the BiDi flags below aren't documented. The question is whether we should
    // add some BiDi versions of e.g. fBold and interpret these sprms here like plain
    // sprmCFBold. For now I just ignore them, as the only user of wv2 is Words, and
    // Words is intelligent enough to support BiDi "the right way." (Werner)
    case SPRM::sprmCFBiDi:
        // ###### Undocumented
        // wvlog << "Warning: sprmCFBiDi not implemented" << Qt::endl;
        break;
    case SPRM::sprmCFDiacColor:
        // ###### Undocumented
        // wvlog << "Warning: sprmCFDiacColor not implemented" << Qt::endl;
        break;
    case SPRM::sprmCFBoldBi:
        // ###### Undocumented
        // wvlog << "Warning: sprmCFBoldBi not implemented" << Qt::endl;
        break;
    case SPRM::sprmCFItalicBi:
        // ###### Undocumented
        // wvlog << "Warning: sprmCFItalicBi not implemented" << Qt::endl;
        break;
    case SPRM::sprmCFtcBi:
        // ###### Undocumented
        // wvlog << "Warning: sprmCFtcBi not implemented" << Qt::endl;
        break;
    case SPRM::sprmCLidBi:
        // OOo does something with that flag.
        wvlog << "Warning: sprmCLidBi not implemented (no documentation available)" << Qt::endl;
        break;
    case SPRM::sprmCIcoBi:
        // ###### Undocumented
        // wvlog << "Warning: sprmCIcoBi not implemented" << Qt::endl;
        break;
    case SPRM::sprmCHpsBi:
        // OOo does something with that flag.
        wvlog << "Warning: sprmCHpsBi not implemented (no documentation available)" << Qt::endl;
        break;
    case SPRM::sprmCDispFldRMark: {
        if (*ptr != 39) {
            wvlog << "Warning: sprmCDispFldRMark has a different length than 39" << Qt::endl;
        } else {
            fDispFldRMark = *(ptr + 1) == 1;
            ibstDispFldRMark = readS16(ptr + 2);
            dttmPropRMark.readPtr(ptr + 4);
            for (int i = 0; i < 16; ++i)
                xstDispFldRMark[i] = readU16(ptr + 8 + i * sizeof(U16));
        }
        break;
    }
    case SPRM::sprmCIbstRMarkDel:
        ibstRMarkDel = readS16(ptr);
        break;
    case SPRM::sprmCDttmRMarkDel:
        dttmRMarkDel.readPtr(ptr);
        break;
    case SPRM::sprmCBrc:
        readBRC(brc, ptr, version);
        break;
    case SPRM::sprmCShd:
        ptr++;
        shd.read90Ptr(ptr);
        break;
    case SPRM::sprmCShd80:
        shd.readPtr(ptr);
        break;
    case SPRM::sprmCIdslRMarkDel:
        idslRMReasonDel = readS16(ptr);
        break;
    case SPRM::sprmCFUsePgsuSettings:
        if (*ptr < 128) {
            fUsePgsuSettings = *ptr == 1;
        } else if (*ptr == 128 && paragraphStyle) {
            fUsePgsuSettings = paragraphStyle->chp().fUsePgsuSettings;
        } else if (*ptr == 129 && paragraphStyle) {
            fUsePgsuSettings = !(paragraphStyle->chp().fUsePgsuSettings);
        } else {
            wvlog << "Warning: sprmCFSpec couldn't find a style" << Qt::endl;
            fUsePgsuSettings = !fUsePgsuSettings;
        }
        break;
    case SPRM::sprmCCpg:
        // Undocumented, no idea what this variable is for. I changed it to chse in
        // the spec as it made more sense. (Werner)
        // wvlog << "Warning: sprmCCpg not implemented" << Qt::endl;
        break;
    case SPRM::sprmCRgLid0:
    case SPRM::sprmCRgLidUndocumented1: // according to OOo a dup. of sprmCRgLid0
        lidDefault = readU16(ptr);
        break;
    case SPRM::sprmCRgLid1:
        lidFE = readU16(ptr);
        break;
    case SPRM::sprmCIdctHint:
        idct = *ptr;
        break;
    // Fall-through intended
    case SPRM::sprmCUndocumented1:
    case SPRM::sprmCUndocumented2:
        break; // They are not documented but they are skipped correctly
    case SPRM::sprmCFELayout: {
        // skipping cb
        U16 ufel = readU16(ptr + sizeof(U8));
        fTNY = ufel;
        // skip to compress;
        ufel >>= 12;
        fTNYCompress = ufel;
        break;
    }
    case SPRM::sprmCPbiIBullet:
        picBulletCP = readU16(ptr);
        wvlog << "=> picBulletCP:" << picBulletCP;
        break;
    case SPRM::sprmCPbiGrf:
        fPicBullet = (*ptr & 0x01);
        fNoAutoSize = (*ptr & 0x02) >> 1;
        wvlog << "=> fPicBullet:" << fPicBullet << "| fNoAutoSize:" << fNoAutoSize << "| check:" << *ptr;
        break;
    default:
        wvlog << "Huh? None of the defined sprms matches 0x" << Qt::hex << sprm << Qt::dec << "... trying to skip anyway" << Qt::endl;
        break;
    }
    return static_cast<S16>(sprmLength); // length of the SPRM
}

// Apply a PICF grpprl of a given size ("count" bytes long)
void PICF::apply(const U8 *grpprl, U16 count, const Style *style, const StyleSheet *styleSheet, OLEStreamReader *dataStream, WordVersion version)
{
    // There should be only PICF sprms in the grpprl we get
    SPRM::apply<PICF>(this, &PICF::applyPICFSPRM, grpprl, count, style, styleSheet, dataStream, version);
}

void PICF::applyExceptions(const U8 * /*exceptions*/, const StyleSheet * /*stylesheet*/, OLEStreamReader * /*dataStream*/, WordVersion /*version*/)
{
    // ### CHECK: Do we need that at all?
}

// Returns -1 if this wasn't a PICF sprm and it returns the length
// of the applied sprm if it was successful
S16 PICF::applyPICFSPRM(const U8 *ptr, const Style * /*style*/, const StyleSheet * /*styleSheet*/, OLEStreamReader * /*dataStream*/, WordVersion version)
{
    U16 sprmLength;
    const U16 sprm(getSPRM(&ptr, version, sprmLength));

    // Is it a PICF sprm?
    if (((sprm & 0x1C00) >> 10) != 3) {
        wvlog << "Warning: You're trying to apply a non PICF sprm to a PICF." << Qt::endl;
        return -1;
    }
    // which one? ;)
    switch (sprm) {
    case SPRM::sprmNoop:
        wvlog << "Huh? Found a sprmNoop..." << Qt::endl;
        break;
    case SPRM::sprmPicBrcl:
        brcl = *ptr;
        break;
    case SPRM::sprmPicScale:
        if (*ptr != 12)
            wvlog << "Warning: sprmPicScale has a different size (" << static_cast<int>(*ptr) << ") than expected (12)." << Qt::endl;
        mx = readU16(ptr + 1);
        my = readU16(ptr + 3);
        dxaCropLeft = readU16(ptr + 5);
        dyaCropTop = readU16(ptr + 7);
        dxaCropRight = readU16(ptr + 9);
        dyaCropBottom = readU16(ptr + 11);
        break;
    case SPRM::sprmPicBrcTop:
        readBRC(brcTop, ptr, version);
        break;
    case SPRM::sprmPicBrcLeft:
        readBRC(brcLeft, ptr, version);
        break;
    case SPRM::sprmPicBrcBottom:
        readBRC(brcBottom, ptr, version);
        break;
    case SPRM::sprmPicBrcRight:
        readBRC(brcRight, ptr, version);
        break;
    default:
        wvlog << "Huh? None of the defined sprms matches 0x" << Qt::hex << sprm << Qt::dec << "... trying to skip anyway" << Qt::endl;
        break;
    }
    return static_cast<S16>(sprmLength); // length of the SPRM
}

// Apply a SEP grpprl of a given size ("count" bytes long)
void SEP::apply(const U8 *grpprl, U16 count, const Style *style, const StyleSheet *styleSheet, OLEStreamReader *dataStream, WordVersion version)
{
    // There should be only SEP sprms in the grpprl we get
    SPRM::apply<SEP>(this, &SEP::applySEPSPRM, grpprl, count, style, styleSheet, dataStream, version);
}

void SEP::applyExceptions(const U8 *exceptions, const StyleSheet *styleSheet, OLEStreamReader *dataStream, WordVersion version)
{
    if (exceptions == nullptr)
        return;
    U16 cb = readU16(exceptions);
    exceptions += 2;
    apply(exceptions, cb, nullptr, styleSheet, dataStream, version);
}

// Returns -1 if this wasn't a SEP sprm and it returns the length
// of the applied sprm if it was successful
S16 SEP::applySEPSPRM(const U8 *ptr, const Style * /*style*/, const StyleSheet * /*styleSheet*/, OLEStreamReader * /*dataStream*/, WordVersion version)
{
    U16 sprmLength;
    const U16 sprm(getSPRM(&ptr, version, sprmLength));

    // Is it a SEP sprm?
    if (((sprm & 0x1C00) >> 10) != 4) {
        wvlog << "Warning: You're trying to apply a non SEP sprm to a SEP." << Qt::endl;
        return -1;
    }
    // which one? ;)
    switch (sprm) {
    case SPRM::sprmNoop:
        wvlog << "Huh? Found a sprmNoop..." << Qt::endl;
        break;
    case SPRM::sprmScnsPgn:
        cnsPgn = *ptr;
        break;
    case SPRM::sprmSiHeadingPgn:
        iHeadingPgn = *ptr;
        break;
    case SPRM::sprmSOlstAnm:
        if (version == Word8)
            olstAnm.readPtr(ptr + 1); // variable length, skip length byte
        else
            olstAnm = toWord97(Word95::OLST(ptr + 1));
        break;
    case SPRM::sprmSDxaColWidth:
        wvlog << "Warning: sprmSDxaColWidth not implemented" << Qt::endl;
        break;
    case SPRM::sprmSDxaColSpacing:
        wvlog << "Warning: sprmSDxaColSpacing not implemented" << Qt::endl;
        break;
    case SPRM::sprmSFEvenlySpaced:
        fEvenlySpaced = *ptr == 1;
        break;
    case SPRM::sprmSFProtected:
        fUnlocked = *ptr == 1;
        break;
    case SPRM::sprmSDmBinFirst:
        dmBinFirst = readU16(ptr);
        break;
    case SPRM::sprmSDmBinOther:
        dmBinOther = readU16(ptr);
        break;
    case SPRM::sprmSBkc:
        bkc = *ptr;
        break;
    case SPRM::sprmSFTitlePage:
        fTitlePage = *ptr == 1;
        break;
    case SPRM::sprmSCcolumns:
        ccolM1 = readS16(ptr);
        break;
    case SPRM::sprmSDxaColumns:
        dxaColumns = readS16(ptr);
        break;
    case SPRM::sprmSFAutoPgn:
        fAutoPgn = *ptr == 1;
        break;
    case SPRM::sprmSNfcPgn:
        nfcPgn = *ptr;
        break;
    case SPRM::sprmSDyaPgn:
        dyaPgn = readS16(ptr);
        break;
    case SPRM::sprmSDxaPgn:
        dxaPgn = readS16(ptr);
        break;
    case SPRM::sprmSFPgnRestart:
        fPgnRestart = *ptr == 1;
        break;
    case SPRM::sprmSFEndnote:
        fEndNote = *ptr == 1;
        break;
    case SPRM::sprmSLnc:
        lnc = *ptr;
        break;
    case SPRM::sprmSGprfIhdt:
        grpfIhdt = *ptr;
        break;
    case SPRM::sprmSNLnnMod:
        nLnnMod = readU16(ptr);
        break;
    case SPRM::sprmSDxaLnn:
        dxaLnn = readS16(ptr);
        break;
    case SPRM::sprmSDyaHdrTop:
        dyaHdrTop = readU16(ptr);
        break;
    case SPRM::sprmSDyaHdrBottom:
        dyaHdrBottom = readU16(ptr);
        break;
    case SPRM::sprmSLBetween:
        fLBetween = *ptr == 1;
        break;
    case SPRM::sprmSVjc:
        vjc = *ptr;
        break;
    case SPRM::sprmSLnnMin:
        lnnMin = readS16(ptr);
        break;
    case SPRM::sprmSPgnStart:
        pgnStart = readU16(ptr);
        break;
    case SPRM::sprmSBOrientation:
        dmOrientPage = *ptr;
        break;
    case SPRM::sprmSBCustomize:
        wvlog << "Warning: sprmSBCustomize not implemented" << Qt::endl;
        break;
    case SPRM::sprmSXaPage:
        xaPage = readU16(ptr);
        break;
    case SPRM::sprmSYaPage:
        yaPage = readU16(ptr);
        break;
    case SPRM::sprmSDxaLeft:
        dxaLeft = readU16(ptr);
        break;
    case SPRM::sprmSDxaRight:
        dxaRight = readU16(ptr);
        break;
    case SPRM::sprmSDyaTop:
        dyaTop = readS16(ptr);
        break;
    case SPRM::sprmSDyaBottom:
        dyaBottom = readS16(ptr);
        break;
    case SPRM::sprmSDzaGutter:
        dzaGutter = readU16(ptr);
        break;
    case SPRM::sprmSDmPaperReq:
        dmPaperReq = readU16(ptr);
        break;
    case SPRM::sprmSPropRMark:
        fPropRMark = *(ptr + 1);
        ibstPropRMark = readS16(ptr + 2);
        dttmPropRMark.readPtr(ptr + 4);
        break;
    case SPRM::sprmSFBiDi:
        wvlog << "Warning: sprmSFBiDi not implemented" << Qt::endl;
        break;
    case SPRM::sprmSFFacingCol:
        wvlog << "Warning: sprmSFFacingCol not implemented" << Qt::endl;
        break;
    case SPRM::sprmSFRTLGutter:
        wvlog << "Warning: sprmSFRTLGutter not implemented" << Qt::endl;
        break;
    case SPRM::sprmSBrcTop:
        readBRC(brcTop, ptr, version);
        break;
    case SPRM::sprmSBrcLeft:
        readBRC(brcLeft, ptr, version);
        break;
    case SPRM::sprmSBrcBottom:
        readBRC(brcBottom, ptr, version);
        break;
    case SPRM::sprmSBrcRight:
        readBRC(brcRight, ptr, version);
        break;
    case SPRM::sprmSPgbProp: {
        U16 pgbProp = readU16(ptr);
        pgbApplyTo = pgbProp;
        pgbProp >>= 3;
        pgbPageDepth = pgbProp;
        pgbProp >>= 2;
        pgbOffsetFrom = pgbProp;
        pgbProp >>= 3;
        unused74_8 = pgbProp;
        break;
    }
    case SPRM::sprmSDxtCharSpace:
        dxtCharSpace = readS32(ptr);
        break;
    case SPRM::sprmSDyaLinePitch:
        dyaLinePitch = readS32(ptr);
        break;
    case SPRM::sprmSClm:
        clm = readU16(ptr);
        break;
    case SPRM::sprmSNfcFtnRef:
        nfcFtnRef = readU16(ptr);
        break;
    case SPRM::sprmSNfcEdnRef:
        nfcEdnRef = readU16(ptr);
        break;
    case SPRM::sprmSTextFlow:
        wTextFlow = readU16(ptr);
        break;
    default:
        wvlog << "Huh? None of the defined sprms matches 0x" << Qt::hex << sprm << Qt::dec << "... trying to skip anyway" << Qt::endl;
        break;
    }
    return static_cast<S16>(sprmLength); // length of the SPRM
}

// Apply a TAP grpprl (or at least the TAP properties of a PAP/TAP grpprl)
// of a given size ("count" bytes long)
void TAP::apply(const U8 *grpprl, U16 count, const Style *style, const StyleSheet *styleSheet, OLEStreamReader *dataStream, WordVersion version)
{
    // There should be mostly TAP sprms in the grpprl we get, and we
    // have to ignore the remaining PAP sprms, just what the template does
    SPRM::apply<TAP>(this, &TAP::applyTAPSPRM, grpprl, count, style, styleSheet, dataStream, version);
}

void TAP::applyExceptions(const U8 * /*exceptions*/, const StyleSheet * /*stylesheet*/, OLEStreamReader * /*dataStream*/, WordVersion /*version*/)
{
    // ### TODO -- is that needed at all?
}

namespace
{
void cropIndices(U8 &itcFirst, U8 &itcLim, U8 size)
{
    if (itcFirst >= size) {
        wvlog << "Warning: itcFirst out of bounds" << Qt::endl;
        itcFirst = size - 1;
    }
    if (itcLim > size) {
        wvlog << "Warning: itcLim out of bounds" << Qt::endl;
        itcLim = size;
    }
}
}

// Returns -1 if this wasn't a TAP sprm and it returns the length
// of the applied sprm if it was successful
S16 TAP::applyTAPSPRM(const U8 *ptr, const Style *style, const StyleSheet *styleSheet, OLEStreamReader *dataStream, WordVersion version)
{
    U16 sprmLength;
    const U16 sprm(getSPRM(&ptr, version, sprmLength));

#ifdef WV2_DEBUG_SPRMS
    wvlog << "sprm: 0x" << hex << sprm << dec << Qt::endl;
    wvlog << "sprmLength: " << sprmLength << Qt::endl;
#endif
    // Is it a TAP sprm? Not really an error if it's none, as all TAP sprms
    // live inside PAP grpprls
    if (((sprm & 0x1C00) >> 10) != 5 && (sprm != SPRM::sprmPHugePapx) && (sprm != SPRM::sprmPHugePapx2) && (sprm != SPRM::sprmPTableProps)) {
#ifdef WV2_DEBUG_SPRMS
        wvlog << "Warning: You're trying to apply a non TAP sprm to a TAP. Not necessarily bad." << Qt::endl;
#endif
        return -1;
    }
    // which one? ;)
    switch (sprm) {
    case SPRM::sprmNoop:
        wvlog << "Huh? Found a sprmNoop..." << Qt::endl;
        break;
    case SPRM::sprmTJc:
        jc = readS16(ptr);
        break;
    case SPRM::sprmTDxaLeft: {
        // NOTE: no recalculation of the horizontal origin, use rgdxaCenter[0]

        //         const S16 dxaNew = readS16( ptr ) - ( rgdxaCenter[ 0 ] + dxaGapHalf );
        //         std::for_each( rgdxaCenter.begin(), rgdxaCenter.end(),
        //                        [=](S16 &item) { item += dxaNew; } );

        dxaLeft = readS16(ptr);
#ifdef WV2_DEBUG_SPRMS
        wvlog << "dxaLeft: " << dxaLeft << Qt::endl;
#endif
        break;
    }
    case SPRM::sprmTDxaGapHalf: {
        // NOTE: no recalculation of the horizontal origin, use rgdxaCenter[0]

        //         const S16 dxaGapHalfNew = readS16( ptr );
        //         if ( !rgdxaCenter.empty() ) {
        //             rgdxaCenter[ 0 ] += dxaGapHalf - dxaGapHalfNew;
        //         }

        dxaGapHalf = readS16(ptr);
#ifdef WV2_DEBUG_SPRMS
        wvlog << "dxaGapHalf: " << dxaGapHalf << Qt::endl;
#endif
        break;
    }
    case SPRM::sprmTFCantSplit:
        fCantSplit = *ptr == 1;
        break;
    case SPRM::sprmTTableHeader:
        fTableHeader = *ptr == 1;
        break;
    case SPRM::sprmTTableBorders80: {
        const U8 inc = version == Word8 ? Word97::BRC::sizeOf97 : Word95::BRC::sizeOf;
        for (int i = 0; i < 6; ++i) {
            // skip the leading size byte
            readBRC(rgbrcTable[i], ptr + 1 + i * inc, version);
        }
        break;
    }
    case SPRM::sprmTTableBorders: {
        const U8 inc = version == Word8 ? Word97::BRC::sizeOf : Word95::BRC::sizeOf;
        for (int i = 0; i < 6; ++i)
            // skip the leading size byte
            rgbrcTable[i].read90Ptr(ptr + 1 + i * inc);
        break;
    }
    case SPRM::sprmTDefTable10:
        wvlog << "Warning: sprmTDefTable10 is obsolete" << Qt::endl;
        break;
    case SPRM::sprmTDyaRowHeight:
        dyaRowHeight = readS16(ptr);
        break;
    case SPRM::sprmTDefTable: {
        if (itcMac != 0) {
            wvlog << "Bug: Assumption about sprmTDefTable not true" << Qt::endl;
            break;
        }
        int remainingLength = readU16(ptr) - 1;
        itcMac = *(ptr + 2);

        remainingLength -= 2 * (itcMac + 1);
        if (remainingLength < 0) {
            wvlog << "Bug: Not even enough space for the dxas!" << Qt::endl;
            break;
        }
        const U8 *myPtr = ptr + 3;
        const U8 *myLim = ptr + 3 + 2 * (itcMac + 1);
        while (myPtr < myLim) {
            rgdxaCenter.push_back(readS16(myPtr));
            myPtr += 2;
        }
#ifdef WV2_DEBUG_SPRMS
        wvlog << "rgdxaCenter[0]: " << rgdxaCenter[0] << Qt::endl;
#endif
        const int tcSize = version == Word8 ? Word97::TC::sizeOf : Word95::TC::sizeOf;
        myLim = myPtr + (remainingLength / tcSize) * tcSize;
        while (myPtr < myLim) {
            if (version == Word8) {
                rgtc.push_back(TC(myPtr));
            } else {
                rgtc.push_back(toWord97(Word95::TC(myPtr)));
            }
            myPtr += tcSize;
        }
        if (rgtc.size() < static_cast<std::vector<S16>::size_type>(itcMac)) {
            rgtc.insert(rgtc.end(), itcMac - rgtc.size(), TC());
        }
        // default shading
        rgshd.insert(rgshd.begin(), itcMac, SHD());
        break;
    }
    case SPRM::sprmTDefTableShd80: {
        const U8 *myPtr = ptr + 1;
        const U8 *myLim = ptr + 1 + *ptr;
        rgshd.clear();
        while (myPtr < myLim) {
            rgshd.push_back(SHD(myPtr));
            myPtr += SHD::sizeOf;
        }
        if (rgshd.size() < static_cast<std::vector<S16>::size_type>(itcMac)) {
            rgshd.insert(rgshd.end(), itcMac - rgshd.size(), SHD());
        }
        break;
    }
    case SPRM::sprmTDefTableShd: {
        const U8 *myPtr = ptr + 1;
        const U8 *myLim = ptr + 1 + *ptr;
        rgshd.clear();
        while (myPtr < myLim) {
            SHD shd;
            shd.read90Ptr(myPtr);
            rgshd.push_back(shd);
            myPtr += SHD::sizeOf;
        }
        if (rgshd.size() < static_cast<std::vector<S16>::size_type>(itcMac)) {
            rgshd.insert(rgshd.end(), itcMac - rgshd.size(), SHD());
        }
        break;
    }
    case SPRM::sprmTDefTableShd2nd: {
        wvlog << "Warning: sprmTDefTableShd2nd not implemented" << Qt::endl;
        break;
    }
    case SPRM::sprmTDefTableShd3rd: {
        wvlog << "Warning: sprmTDefTableShd3rd not implemented" << Qt::endl;
        break;
    }
    case SPRM::sprmTTlp:
        tlp.readPtr(ptr);
        break;
    case SPRM::sprmTFBiDi:
        fBiDi = readU16(ptr);
        break;
    case SPRM::sprmTPc: {
        pcVert = (*ptr & 0x30) >> 4;
        pcHorz = (*ptr & 0xC0) >> 6;
        break;
    }
    case SPRM::sprmTDxaFromText:
        dxaFromText = readU16(ptr);
        textWrap = 1;
        break;
    case SPRM::sprmTDyaFromText:
        dyaFromText = readU16(ptr);
        textWrap = 1;
        break;
    case SPRM::sprmTDxaFromTextRight:
        dxaFromTextRight = readU16(ptr);
        textWrap = 1;
        break;
    case SPRM::sprmTDyaFromTextBottom:
        dyaFromTextBottom = readU16(ptr);
        textWrap = 1;
        break;
    case SPRM::sprmTDxaAbs:
        dxaAbs = readS16(ptr);
        break;
    case SPRM::sprmTDyaAbs:
        dyaAbs = readS16(ptr);
        break;
    case SPRM::sprmTHTMLProps:
        wvlog << "Warning: sprmTHTMLProps not implemented" << Qt::endl;
        break;
    case SPRM::sprmTSetBrc80: {
        const U8 *myPtr(version == Word8 ? ptr + 1 : ptr); // variable size byte for Word 8!
        U8 itcFirst = *myPtr;
        U8 itcLim = *(myPtr + 1);
        cropIndices(itcFirst, itcLim, rgtc.size());
        const U8 flags = *(myPtr + 2);
        BRC brc;
        readBRC(brc, myPtr + 3, version);

        for (; itcFirst < itcLim; ++itcFirst) {
            if (flags & 0x01)
                rgtc[itcFirst].brcTop = brc;
            if (flags & 0x02)
                rgtc[itcFirst].brcLeft = brc;
            if (flags & 0x04)
                rgtc[itcFirst].brcBottom = brc;
            if (flags & 0x08)
                rgtc[itcFirst].brcRight = brc;
        }
        break;
    }
    case SPRM::sprmTInsert: {
        // NOTE: don't process before sprmTDefTable
        if (itcMac == 0) {
            wvlog << "Bug: Assumption about sprmTDefTable not true" << Qt::endl;
            break;
        }
        // Sanity check
        if (static_cast<std::vector<S16>::size_type>(itcMac) + 1 != rgdxaCenter.size()) {
            wvlog << "Bug: Somehow itcMac and the rgdxaCenter.size() aren't in sync anymore!" << Qt::endl;
            itcMac = rgdxaCenter.size() - 1;
        }

        const U8 itcFirst = *ptr;
        const U8 ctc = *(ptr + 1);
        const U16 dxaCol = readU16(ptr + 2);

        if (itcMac < itcFirst) {
            // Shaky, no idea why we would have to subtract ctc here?  The
            // implementation below is a guess from me, but it looks like this
            // never happens in real documents.
            wvlog << "Warning: sprmTInsert: Debug me ########################################" << Qt::endl;

            S16 runningDxaCol = 0;
            if (!rgdxaCenter.empty()) {
                runningDxaCol = rgdxaCenter.back();
            }

            for (; itcMac < itcFirst; ++itcMac) {
                // If the index is bigger than our current array we just fill
                // the hole with dummy cells (dxaCol wide, default TC) as
                // suggested in the documentation
                runningDxaCol += dxaCol;
                rgdxaCenter.push_back(runningDxaCol);
                rgtc.push_back(TC());
            }
        } else {
            S16 runningDxaCol;
            if (itcFirst > 0) {
                runningDxaCol = rgdxaCenter[itcFirst - 1] + dxaCol;
            }
            // preserve the position of the table row
            else {
                runningDxaCol = rgdxaCenter[0];
            }
            for (int i = 0; i < ctc; ++i) {
                std::vector<S16>::iterator dxaIt = rgdxaCenter.begin() + itcFirst + i;
                rgdxaCenter.insert(dxaIt, runningDxaCol);
                runningDxaCol += dxaCol;
            }

            rgtc.insert(rgtc.begin() + itcFirst, ctc, TC());
            rgshd.insert(rgshd.begin() + itcFirst, ctc, SHD());
            itcMac += ctc;

            // Adjust all successive items (+= ctc * dxaCol)
            std::for_each(rgdxaCenter.begin() + itcFirst + ctc, rgdxaCenter.end(), [=](S16 &item) {
                item += ctc * dxaCol;
            });
        }
        break;
    }
    case SPRM::sprmTDelete: {
        U8 itcFirst = *ptr;
        U8 itcLim = *(ptr + 1);
        cropIndices(itcFirst, itcLim, rgdxaCenter.size());

        rgdxaCenter.erase(rgdxaCenter.begin() + itcFirst, rgdxaCenter.begin() + itcLim);
        rgtc.erase(rgtc.begin() + itcFirst, rgtc.begin() + itcLim);
        rgshd.erase(rgshd.begin() + itcFirst, rgshd.begin() + itcLim);

        itcMac -= itcLim - itcFirst;
        break;
    }
    case SPRM::sprmTDxaCol: {
        // NOTE: don't process before sprmTDefTable
        if (itcMac == 0) {
            wvlog << "Bug: Assumption about sprmTDefTable not true" << Qt::endl;
            break;
        }

        U8 itcFirst = *ptr;
        U8 itcLim = *(ptr + 1);
        const S16 dxaCol = readS16(ptr + 2);

        cropIndices(itcFirst, itcLim, rgdxaCenter.size());
        S16 shift = 0;
        for (; itcFirst < itcLim; ++itcFirst) {
            shift += rgdxaCenter[itcFirst + 1] - rgdxaCenter[itcFirst] - dxaCol;
            rgdxaCenter[itcFirst + 1] = rgdxaCenter[itcFirst] + dxaCol;
        }
        // Adjust all the following columns
        ++itcFirst;
        std::for_each(rgdxaCenter.begin() + itcFirst, rgdxaCenter.end(), [=](S16 &item) {
            item -= shift;
        });
        break;
    }
    case SPRM::sprmTMerge: {
        wvlog << "Debug me (sprmTMerge) #########################################################" << Qt::endl;
        U8 itcFirst = *ptr;
        U8 itcLim = *(ptr + 1);
        cropIndices(itcFirst, itcLim, rgtc.size());

        rgtc[itcFirst++].fFirstMerged = 1;
        for (; itcFirst < itcLim; ++itcFirst) {
            rgtc[itcFirst].fMerged = 1;
        }
        break;
    }
    case SPRM::sprmTSplit: {
        wvlog << "Debug me (sprmTSplit) #########################################################" << Qt::endl;
        U8 itcFirst = *ptr;
        U8 itcLim = *(ptr + 1);
        cropIndices(itcFirst, itcLim, rgtc.size());

        std::vector<TC>::iterator it = rgtc.begin() + itcFirst;
        std::vector<TC>::const_iterator end = rgtc.begin() + itcLim;
        for (; it != end; ++it) {
            (*it).fFirstMerged = 0;
            (*it).fMerged = 0;
        }
        break;
    }
    case SPRM::sprmTSetBrc10:
        wvlog << "Warning: sprmTSetBrc10 doesn't make sense for Word97 structures" << Qt::endl;
        break;
    case SPRM::sprmTSetShd: {
        U8 itcFirst = *ptr;
        U8 itcLim = *(ptr + 1);
        cropIndices(itcFirst, itcLim, rgshd.size());
        const SHD shd(ptr + 2);

        std::fill_n(rgshd.begin() + itcFirst, itcLim - itcFirst, shd);
        break;
    }
    case SPRM::sprmTSetShdOdd: {
        U8 itcFirst = *ptr;
        U8 itcLim = *(ptr + 1);
        cropIndices(itcFirst, itcLim, rgshd.size());
        const SHD shd(ptr + 2);

        for (; itcFirst < itcLim; ++itcFirst) {
            if (itcFirst & 0x01) {
                rgshd[itcFirst] = shd;
            }
        }
        break;
    }
    case SPRM::sprmTTextFlow:
        wvlog << "Warning: sprmTTextFlow is undocumented. Please send this document to trobin@kde.org." << Qt::endl;
        break;
    case SPRM::sprmTDiagLine:
        wvlog << "Warning: sprmTDiagLine is undocumented. Please send this document to trobin@kde.org." << Qt::endl;
        break;
    case SPRM::sprmTVertMerge: {
        const U8 index = *ptr;
        const U8 flags = *(ptr + 1);
        if (index < rgtc.size()) {
            rgtc[index].fVertMerge = flags;
            rgtc[index].fVertRestart = (flags & 0x02) >> 1;
        } else {
            wvlog << "Warning: sprmTVertMerge: Out of bounds access" << Qt::endl;
        }
        break;
    }
    case SPRM::sprmTVertAlign: {
        U8 itcFirst = *ptr;
        U8 itcLim = *(ptr + 1);
        cropIndices(itcFirst, itcLim, rgtc.size());
        const U8 vertAlign = *(ptr + 2);

        for (; itcFirst < itcLim; ++itcFirst) {
            rgtc[itcFirst].vertAlign = vertAlign;
        }
        break;
    }
    case SPRM::sprmTSetBrc: {
        // variable size byte for Word 8!
        const U8 *myPtr(version == Word8 ? ptr + 1 : ptr);
        U8 itcFirst = *myPtr;
        U8 itcLim = *(myPtr + 1);
        cropIndices(itcFirst, itcLim, rgtc.size());
        const U8 flags = *(myPtr + 2);
        BRC brc;
        brc.read90Ptr(myPtr + 3);

        for (; itcFirst < itcLim; ++itcFirst) {
            if (flags & 0x01) {
                rgtc[itcFirst].brcTop = brc;
            }
            if (flags & 0x02) {
                rgtc[itcFirst].brcLeft = brc;
            }
            if (flags & 0x04) {
                rgtc[itcFirst].brcBottom = brc;
            }
            if (flags & 0x08) {
                rgtc[itcFirst].brcRight = brc;
            }
            if (flags & 0x10) {
                rgtc[itcFirst].brcTL2BR = brc;
            }
            if (flags & 0x20) {
                rgtc[itcFirst].brcTR2BL = brc;
            }
        }
        break;
    }
    case SPRM::sprmTBrcTopCv: {
        // variable size byte for Word 8!
        const U8 *myPtr(version == Word8 ? ptr + 1 : ptr);
        for (uint i = 0; i < 64 && i < rgtc.size(); ++i) {
            rgtc[i].brcTop.cv = ((*(myPtr + i * 4)) << 16) | ((*(myPtr + 1 + i * 4)) << 8) | (*(myPtr + 2 + i * 4));
        }
        break;
    }
    case SPRM::sprmTBrcLeftCv: {
        // variable size byte for Word 8!
        const U8 *myPtr(version == Word8 ? ptr + 1 : ptr);
        for (uint i = 0; i < 64 && i < rgtc.size(); ++i) {
            rgtc[i].brcLeft.cv = ((*(myPtr + i * 4)) << 16) | ((*(myPtr + 1 + i * 4)) << 8) | (*(myPtr + 2 + i * 4));
        }
        break;
    }
    case SPRM::sprmTBrcRightCv: {
        // variable size byte for Word 8!
        const U8 *myPtr(version == Word8 ? ptr + 1 : ptr);
        for (uint i = 0; i < 64 && i < rgtc.size(); ++i) {
            rgtc[i].brcRight.cv = ((*(myPtr + i * 4)) << 16) | ((*(myPtr + 1 + i * 4)) << 8) | (*(myPtr + 2 + i * 4));
        }
        break;
    }
    case SPRM::sprmTBrcBottomCv: {
        // variable size byte for Word 8!
        const U8 *myPtr(version == Word8 ? ptr + 1 : ptr);
        for (uint i = 0; i < 64 && i < rgtc.size(); ++i) {
            rgtc[i].brcBottom.cv = ((*(myPtr + i * 4)) << 16) | ((*(myPtr + 1 + i * 4)) << 8) | (*(myPtr + 2 + i * 4));
        }
        break;
    }
    case SPRM::sprmTCellPadding:
        wvlog << "Warning: sprmTCellPadding not implemented" << Qt::endl;
        //         if ( *ptr == 6 ) {
        //             const U8 itcFirst( *( ptr + 1 ) );
        //             for ( int i = 0; i < 6; ++i )
        //                 wvlog << "    byte " << i << ": " << hex << ( int )*( ptr + i ) << dec << Qt::endl;
        //         }
        //         else {
        //             wvlog << "Warning: sprmTCellPadding with unusual length=" <<
        //                   static_cast<int>( *ptr ) << Qt::endl;
        //         }
        break;
    case SPRM::sprmTCellSpacingDefault:
        wvlog << "Warning: sprmTCellSpacingDefault not implemented" << Qt::endl;
        break;
    case SPRM::sprmTCellPaddingDefault:
        if (*ptr == 6) {
            U8 itcFirst = *(ptr + 1);
            U8 itcLim = *(ptr + 2);
            U8 grfbrc = *(ptr + 3);
            U8 ftsWidth = *(ptr + 4);
            const U16 wWidth = readU16(ptr + 5);

            if ((itcFirst != 0) || (itcLim != 1)) {
                wvlog << "Warning: wrong itcFirst or itcLim value";
            }

            switch (ftsWidth) {
            case Word97::ftsNil:
                wvlog << "Note: wWidth is not used and must be zero";
                break;
            case Word97::ftsAuto:
            case Word97::ftsPercent:
            case Word97::ftsDxaSys:
                wvlog << "Note: this value of ftsWidth is not allowed";
                break;
            default:
                if (grfbrc == (Word97::fbrcLeft | Word97::fbrcRight)) {
                    padHorz = wWidth;
                } else if (grfbrc == (Word97::fbrcTop | Word97::fbrcBottom)) {
                    padVert = wWidth;
                } else {
                    padHorz = wWidth;
                    padVert = wWidth;
                }
                break;
            }
        } else {
            wvlog << "Warning: sprmTCellPaddingDefault with unusual length=" << static_cast<int>(*ptr) << Qt::endl;
        }
        break;
    case SPRM::sprmTUndocumented1:
    case SPRM::sprmTUndocumented2:
        wvlog << "Warning: undocumented SPRM";
        break;
    case SPRM::sprmTSetShdTable:
        wvlog << "Warning: sprmTSetShdTable not implemented" << Qt::endl;
        break;
    case SPRM::sprmTUndocumented10:
    case SPRM::sprmTUndocumented11:
    case SPRM::sprmTUndocumented12:
        wvlog << "Warning: undocumented SPRM";
        break;
    case SPRM::sprmTWidthIndent: {
        // MS-DOC, page 362 of 609
        const U8 ftsWidth = *ptr;
        const S16 wWidth = readS16(ptr + 1);

        switch (ftsWidth) {
        case Word97::ftsNil:
        case Word97::ftsAuto:
            wvlog << "Note: wWidth is not used and must be zero";
            break;
        case Word97::ftsPercent:
            wvlog << "Note: this value of ftsWidth is not allowed";
            break;
        default:
            widthIndent = wWidth;
            break;
        }
        break;
    }
    // These are needed in case there are table properties inside the huge papx
    case SPRM::sprmPHugePapx:
    case SPRM::sprmPHugePapx2:
    case SPRM::sprmPTableProps: {
        wvlog << "--> Parsing PrcData" << Qt::endl;
        if (dataStream) {
            dataStream->push();
            dataStream->seek(readU32(ptr), WV2_SEEK_SET);

            const U16 count(dataStream->readU16());
            U8 *grpprl = new U8[count];

            dataStream->read(grpprl, count);
            dataStream->pop();

            apply(grpprl, count, style, styleSheet, dataStream, version);
            delete[] grpprl;
        } else {
            wvlog << "Error: no data stream!" << Qt::endl;
        }
        wvlog << "<-- PrcData parsed successfully" << Qt::endl;
        break;
    }
    default:
        wvlog << "Huh? None of the defined sprms matches 0x" << Qt::hex << sprm << Qt::dec << "... trying to skip anyway" << Qt::endl;
        break;
    }
    return static_cast<S16>(sprmLength); // length of the SPRM
}

// Creates a PRM2 from a plain PRM
PRM2 PRM::toPRM2() const
{
    PRM2 prm;
    prm.fComplex = fComplex;
    prm.igrpprl = (val << 7) | isprm;
    return prm;
}

} // namespace Word97
} // namespace wvWare
