/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* * This file is part of the libmspub project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "MSPUBParser.h" #include #include #include #include #include #include #include #include #include #include #include "Arrow.h" #include "ColorReference.h" #include "Coordinate.h" #include "Dash.h" #include "EscherContainerType.h" #include "EscherFieldIds.h" #include "Fill.h" #include "FillType.h" #include "Line.h" #include "ListInfo.h" #include "MSPUBBlockID.h" #include "MSPUBBlockType.h" #include "MSPUBCollector.h" #include "MSPUBConstants.h" #include "MSPUBContentChunkType.h" #include "MSPUBMetaData.h" #include "Shadow.h" #include "ShapeFlags.h" #include "ShapeType.h" #include "TableInfo.h" #include "VerticalAlign.h" #include "libmspub_utils.h" namespace libmspub { namespace { Underline readUnderline(const unsigned value) { switch (value & 0xff) { case 0x0: return Underline::None; default: MSPUB_DEBUG_MSG(("unknown underline type %u\n", value & 0xff)); MSPUB_FALLTHROUGH; case 0x1: return Underline::Single; case 0x2: return Underline::WordsOnly; case 0x3: return Underline::Double; case 0x4: return Underline::Dotted; case 0x6: return Underline::Thick; case 0x7: return Underline::Dash; case 0x9: return Underline::DotDash; case 0xa: return Underline::DotDotDash; case 0xb: return Underline::Wave; case 0x10: return Underline::ThickWave; case 0x11: return Underline::ThickDot; case 0x12: return Underline::ThickDash; case 0x13: return Underline::ThickDotDash; case 0x14: return Underline::ThickDotDotDash; case 0x15: return Underline::LongDash; case 0x16: return Underline::ThickLongDash; case 0x17: return Underline::DoubleWave; } } } MSPUBParser::MSPUBParser(librevenge::RVNGInputStream *input, MSPUBCollector *collector) : m_input(input), m_length(boost::numeric_cast(getLength(input))), m_collector(collector), m_blockInfo(), m_contentChunks(), m_cellsChunkIndices(), m_pageChunkIndices(), m_shapeChunkIndices(), m_paletteChunkIndices(), m_borderArtChunkIndices(), m_fontChunkIndices(), m_unknownChunkIndices(), m_documentChunkIndex(), m_lastSeenSeqNum(-1), m_lastAddedImage(0), m_alternateShapeSeqNums(), m_escherDelayIndices() { } MSPUBParser::~MSPUBParser() { } bool MSPUBParser::lineExistsByFlagPointer(unsigned *flags, unsigned *geomFlags) { return flags && !(((*flags) & FLAG_USE_LINE) && !((*flags) & FLAG_LINE)) && ((!geomFlags) || !((*geomFlags) & FLAG_GEOM_USE_LINE_OK) || ((*geomFlags) & FLAG_GEOM_LINE_OK)); } unsigned MSPUBParser::getColorIndexByQuillEntry(unsigned entry) { return entry; } short MSPUBParser::getBlockDataLength(unsigned type) // -1 for variable-length block with the data length as the first DWORD { switch (type) { case DUMMY: case 0x5: case 0x8: case 0xa: return 0; case 0x10: case 0x12: case 0x18: case 0x1a: case 0x07: return 2; case 0x20: case 0x22: case 0x58: case 0x68: case 0x70: case 0xb8: return 4; case 0x28: return 8; case 0x38: return 16; case 0x48: return 24; case STRING_CONTAINER: case 0x80: case 0x82: case GENERAL_CONTAINER: case 0x8a: case 0x90: case 0x98: case 0xa0: return -1; } //FIXME: Debug assertion here? Should never get here. MSPUB_DEBUG_MSG(("Block of unknown type seen!\n")); return 0; } bool MSPUBParser::parse() { MSPUB_DEBUG_MSG(("***NOTE***: Where applicable, the meanings of block/chunk IDs and Types printed below may be found in:\n\t***MSPUBBlockType.h\n\t***MSPUBBlockID.h\n\t***MSPUBContentChunkType.h\n*****\n")); if (!m_input->isStructured()) return false; // No check: metadata are not important enough to fail if they can't be parsed parseMetaData(); std::unique_ptr quill(m_input->getSubStreamByName("Quill/QuillSub/CONTENTS")); if (!quill) { MSPUB_DEBUG_MSG(("Couldn't get quill stream.\n")); return false; } if (!parseQuill(quill.get())) { MSPUB_DEBUG_MSG(("Couldn't parse quill stream.\n")); return false; } std::unique_ptr contents(m_input->getSubStreamByName("Contents")); if (!contents) { MSPUB_DEBUG_MSG(("Couldn't get contents stream.\n")); return false; } if (!parseContents(contents.get())) { MSPUB_DEBUG_MSG(("Couldn't parse contents stream.\n")); return false; } std::unique_ptr escherDelay(m_input->getSubStreamByName("Escher/EscherDelayStm")); if (escherDelay) { parseEscherDelay(escherDelay.get()); } std::unique_ptr escher(m_input->getSubStreamByName("Escher/EscherStm")); if (!escher) { MSPUB_DEBUG_MSG(("Couldn't get escher stream.\n")); return false; } if (!parseEscher(escher.get())) { MSPUB_DEBUG_MSG(("Couldn't parse escher stream.\n")); return false; } return m_collector->go(); } ImgType MSPUBParser::imgTypeByBlipType(unsigned short type) { switch (type) { case OFFICE_ART_BLIP_PNG: return PNG; case OFFICE_ART_BLIP_JPEG: return JPEG; case OFFICE_ART_BLIP_JPEGCMYK: return JPEGCMYK; case OFFICE_ART_BLIP_WMF: return WMF; case OFFICE_ART_BLIP_DIB: return DIB; case OFFICE_ART_BLIP_EMF: return EMF; case OFFICE_ART_BLIP_TIFF: return TIFF; case OFFICE_ART_BLIP_PICT: return PICT; } return UNKNOWN; } int MSPUBParser::getStartOffset(ImgType type, unsigned short initial) { bool oneUid = true; int offset = 0x11; unsigned short recInstance = initial >> 4; switch (type) { case WMF: oneUid = recInstance == 0x216; offset = 0x34; break; case EMF: oneUid = recInstance == 0x3D4; offset = 0x34; break; case PNG: oneUid = recInstance == 0x6E0; offset = 0x11; break; case JPEG: oneUid = recInstance == 0x46A || recInstance == 0x6E2; offset = 0x11; break; case JPEGCMYK: oneUid = recInstance == 0x46B || recInstance == 0x6E3; offset = 33; break; case DIB: oneUid = recInstance == 0x7A8; offset = 0x11; break; case TIFF: oneUid = recInstance == 0x6E4; offset = 0x11; break; default: break; } return offset + (oneUid ? 0 : 0x10); } bool MSPUBParser::parseEscherDelay(librevenge::RVNGInputStream *input) { while (stillReading(input, (unsigned long)-1)) { EscherContainerInfo info = parseEscherContainer(input); const ImgType imgType = imgTypeByBlipType(info.type); if (imgType != UNKNOWN) { librevenge::RVNGBinaryData img; unsigned long toRead = info.contentsLength; input->seek(input->tell() + getStartOffset(imgType, info.initial), librevenge::RVNG_SEEK_SET); while (toRead > 0 && stillReading(input, (unsigned long)-1)) { unsigned long howManyRead = 0; const unsigned char *buf = input->read(toRead, howManyRead); img.append(buf, howManyRead); toRead -= howManyRead; } if (imgType == WMF || imgType == EMF) { img = inflateData(img); } else if (imgType == DIB) { // Reconstruct BMP header // cf. http://en.wikipedia.org/wiki/BMP_file_format , accessed 2012-5-31 librevenge::RVNGInputStream *buf = img.getDataStream(); if (img.size() < 0x2E + 4) { ++m_lastAddedImage; MSPUB_DEBUG_MSG(("Garbage DIB at index 0x%x\n", m_lastAddedImage)); input->seek(info.contentsOffset + info.contentsLength, librevenge::RVNG_SEEK_SET); continue; } buf->seek(0x0E, librevenge::RVNG_SEEK_SET); unsigned short bitsPerPixel = readU16(buf); buf->seek(0x20, librevenge::RVNG_SEEK_SET); unsigned numPaletteColors = readU32(buf); if (numPaletteColors == 0 && bitsPerPixel <= 8) { numPaletteColors = 1; for (int i = 0; i < bitsPerPixel; ++i) { numPaletteColors *= 2; } } librevenge::RVNGBinaryData tmpImg; tmpImg.append((unsigned char)0x42); tmpImg.append((unsigned char)0x4d); tmpImg.append((unsigned char)((img.size() + 14) & 0x000000ff)); tmpImg.append((unsigned char)(((img.size() + 14) & 0x0000ff00) >> 8)); tmpImg.append((unsigned char)(((img.size() + 14) & 0x00ff0000) >> 16)); tmpImg.append((unsigned char)(((img.size() + 14) & 0xff000000) >> 24)); tmpImg.append((unsigned char)0x00); tmpImg.append((unsigned char)0x00); tmpImg.append((unsigned char)0x00); tmpImg.append((unsigned char)0x00); tmpImg.append((unsigned char)(0x36 + 4 * numPaletteColors)); tmpImg.append((unsigned char)0x00); tmpImg.append((unsigned char)0x00); tmpImg.append((unsigned char)0x00); tmpImg.append(img); img = tmpImg; } m_collector->addImage(++m_lastAddedImage, imgType, img); } else { ++m_lastAddedImage; MSPUB_DEBUG_MSG(("Image of unknown type at index 0x%x\n", m_lastAddedImage)); } input->seek(info.contentsOffset + info.contentsLength, librevenge::RVNG_SEEK_SET); } return true; } bool MSPUBParser::parseContents(librevenge::RVNGInputStream *input) { MSPUB_DEBUG_MSG(("MSPUBParser::parseContents\n")); input->seek(0x1a, librevenge::RVNG_SEEK_SET); unsigned trailerOffset = readU32(input); MSPUB_DEBUG_MSG(("MSPUBParser: trailerOffset %.8x\n", trailerOffset)); input->seek(trailerOffset, librevenge::RVNG_SEEK_SET); unsigned trailerLength = readU32(input); for (unsigned i=0; i<3; i++) { MSPUBBlockInfo trailerPart = parseBlock(input); MSPUB_DEBUG_MSG(("Trailer SubBlock %i, startPosition 0x%lx, id %i, type 0x%x, dataLength 0x%lx\n", i+1, trailerPart.startPosition, trailerPart.id, trailerPart.type, trailerPart.dataLength)); if (trailerPart.type == TRAILER_DIRECTORY) { while (stillReading(input, trailerPart.dataOffset + trailerPart.dataLength)) { m_blockInfo.push_back(parseBlock(input)); ++m_lastSeenSeqNum; if (m_blockInfo.back().type == GENERAL_CONTAINER) { if (parseContentChunkReference(input, m_blockInfo.back())) { if (m_contentChunks.size() > 1) { m_contentChunks[m_contentChunks.size() - 2].end = m_contentChunks.back().offset; } } } else(skipBlock(input, m_blockInfo.back())); } if (!m_contentChunks.empty()) { m_contentChunks.back().end = trailerPart.dataOffset + trailerPart.dataLength; } if (!m_documentChunkIndex) { return false; } const ContentChunkReference &documentChunk = m_contentChunks.at(m_documentChunkIndex.get()); for (unsigned int paletteChunkIndex : m_paletteChunkIndices) { const ContentChunkReference &paletteChunk = m_contentChunks.at(paletteChunkIndex); input->seek(paletteChunk.offset, librevenge::RVNG_SEEK_SET); if (! parsePaletteChunk(input, paletteChunk)) { return false; } } for (unsigned int borderArtChunkIndex : m_borderArtChunkIndices) { const ContentChunkReference &baChunk = m_contentChunks.at(borderArtChunkIndex); input->seek(baChunk.offset, librevenge::RVNG_SEEK_SET); if (!parseBorderArtChunk(input, baChunk)) { return false; } } for (unsigned int shapeChunkIndex : m_shapeChunkIndices) { const ContentChunkReference &shapeChunk = m_contentChunks.at(shapeChunkIndex); input->seek(shapeChunk.offset, librevenge::RVNG_SEEK_SET); if (!parseShape(input, shapeChunk)) { return false; } } for (unsigned int fontChunkIndex : m_fontChunkIndices) { const ContentChunkReference &fontChunk = m_contentChunks.at(fontChunkIndex); input->seek(fontChunk.offset, librevenge::RVNG_SEEK_SET); if (!parseFontChunk(input, fontChunk)) { return false; } } input->seek(documentChunk.offset, librevenge::RVNG_SEEK_SET); if (!parseDocumentChunk(input, documentChunk)) { return false; } for (unsigned int pageChunkIndex : m_pageChunkIndices) { const ContentChunkReference &pageChunk = m_contentChunks.at(pageChunkIndex); input->seek(pageChunk.offset, librevenge::RVNG_SEEK_SET); if (!parsePageChunk(input, pageChunk)) { return false; } } } } input->seek(trailerOffset + trailerLength, librevenge::RVNG_SEEK_SET); return true; } #ifdef DEBUG bool MSPUBParser::parseDocumentChunk(librevenge::RVNGInputStream *input, const ContentChunkReference &chunk) #else bool MSPUBParser::parseDocumentChunk(librevenge::RVNGInputStream *input, const ContentChunkReference &) #endif { MSPUB_DEBUG_MSG(("parseDocumentChunk: offset 0x%lx, end 0x%lx\n", input->tell(), chunk.end)); unsigned long begin = input->tell(); unsigned long len = readU32(input); while (stillReading(input, begin + len)) { MSPUBBlockInfo info = parseBlock(input); if (info.id == DOCUMENT_SIZE) { while (stillReading(input, info.dataOffset + info.dataLength)) { MSPUBBlockInfo subInfo = parseBlock(input, true); if (subInfo.id == DOCUMENT_WIDTH) { m_collector->setWidthInEmu(subInfo.data); } else if (subInfo.id == DOCUMENT_HEIGHT) { m_collector->setHeightInEmu(subInfo.data); } } } else if (info.id == DOCUMENT_PAGE_LIST) { input->seek(info.dataOffset + 4, librevenge::RVNG_SEEK_SET); while (stillReading(input, info.dataOffset + info.dataLength)) { MSPUBBlockInfo subInfo = parseBlock(input, true); if (subInfo.id == 0) { m_collector->setNextPage(subInfo.data); } } } else { skipBlock(input, info); } } return true; //FIXME: return false for failure } bool MSPUBParser::parseFontChunk( librevenge::RVNGInputStream *input, const ContentChunkReference &chunk) { unsigned length = readU32(input); while (stillReading(input, chunk.offset + length)) { MSPUBBlockInfo info = parseBlock(input, true); if (info.id == FONT_CONTAINER_ARRAY) { input->seek(info.dataOffset + 4, librevenge::RVNG_SEEK_SET); while (stillReading(input, info.dataOffset + info.dataLength)) { MSPUBBlockInfo subInfo = parseBlock(input, true); if (subInfo.id == 0) { boost::optional name; boost::optional eotOffset; unsigned eotLength = 0; input->seek(subInfo.dataOffset + 4, librevenge::RVNG_SEEK_SET); while (stillReading(input, subInfo.dataOffset + subInfo.dataLength)) { MSPUBBlockInfo subSubInfo = parseBlock(input, true); if (subSubInfo.id == EMBEDDED_FONT_NAME) { name = librevenge::RVNGString(); // drop trailing 0 // TODO: This could be a general problem. Check. const std::size_t len = subSubInfo.stringData.size(); if ((len > 2) && (subSubInfo.stringData[len - 1] == 0) && (subSubInfo.stringData[len - 2] == 0)) { subSubInfo.stringData.pop_back(); subSubInfo.stringData.pop_back(); } appendCharacters(name.get(), subSubInfo.stringData, "UTF-16LE"); } else if (subSubInfo.id == EMBEDDED_EOT) { eotOffset = subSubInfo.dataOffset; eotLength = subSubInfo.dataLength; } } if (bool(name) && bool(eotOffset)) { // skip length, we've already read that // TODO: Why do we not read the data as part of the block info? input->seek(eotOffset.get() + 4, librevenge::RVNG_SEEK_SET); librevenge::RVNGBinaryData data; unsigned long toRead = eotLength; while (toRead > 0 && stillReading(input, (unsigned long)-1)) { unsigned long howManyRead = 0; const unsigned char *buf = input->read(toRead, howManyRead); data.append(buf, howManyRead); toRead -= howManyRead; } m_collector->addEOTFont(name.get(), data); input->seek(subInfo.dataOffset + subInfo.dataLength, librevenge::RVNG_SEEK_SET); } } } } } return true; } bool MSPUBParser::parseBorderArtChunk( librevenge::RVNGInputStream *input, const ContentChunkReference &chunk) { unsigned length = readU32(input); while (stillReading(input, chunk.offset + length)) { MSPUBBlockInfo info = parseBlock(input, true); if (info.id == BA_ARRAY) { input->seek(info.dataOffset + 4, librevenge::RVNG_SEEK_SET); unsigned i = 0; while (stillReading(input, info.dataOffset + info.dataLength)) { MSPUBBlockInfo entry = parseBlock(input, false); while (stillReading(input, entry.dataOffset + entry.dataLength)) { MSPUBBlockInfo subRecord = parseBlock(input, true); if (subRecord.id == BA_IMAGE_ARRAY) { input->seek(subRecord.dataOffset + 4, librevenge::RVNG_SEEK_SET); while (stillReading(input, subRecord.dataOffset + subRecord.dataLength)) { MSPUBBlockInfo subSubRecord = parseBlock(input, false); if (subSubRecord.id == BA_IMAGE_CONTAINER) { MSPUBBlockInfo imgRecord = parseBlock(input, false); if (imgRecord.id == BA_IMAGE) { librevenge::RVNGBinaryData &img = *(m_collector->addBorderImage( WMF, i)); unsigned long toRead = imgRecord.dataLength; while (toRead > 0 && stillReading(input, (unsigned long)-1)) { unsigned long howManyRead = 0; const unsigned char *buf = input->read(toRead, howManyRead); img.append(buf, howManyRead); toRead -= howManyRead; } } } } } else if (subRecord.id == BA_OFFSET_CONTAINER) { input->seek(subRecord.dataOffset + 4, librevenge::RVNG_SEEK_SET); while (stillReading( input, subRecord.dataOffset + subRecord.dataLength)) { MSPUBBlockInfo subSubRecord = parseBlock(input, true); if (subSubRecord.id == BA_OFFSET_ENTRY) { m_collector->setBorderImageOffset(i, subSubRecord.data); } } } } ++i; input->seek(entry.dataOffset + entry.dataLength, librevenge::RVNG_SEEK_SET); } } } return true; } bool MSPUBParser::parsePageChunk(librevenge::RVNGInputStream *input, const ContentChunkReference &chunk) { MSPUB_DEBUG_MSG(("parsePageChunk: offset 0x%lx, end 0x%lx, seqnum 0x%x, parent 0x%x\n", input->tell(), chunk.end, chunk.seqNum, chunk.parentSeqNum)); unsigned long length = readU32(input); PageType type = getPageTypeBySeqNum(chunk.seqNum); if (type == NORMAL) { m_collector->addPage(chunk.seqNum); } while (stillReading(input, chunk.offset + length)) { MSPUBBlockInfo info = parseBlock(input); if (info.id == PAGE_BG_SHAPE) { m_collector->setPageBgShape(chunk.seqNum, info.data); } else if (info.id == PAGE_SHAPES) { parsePageShapeList(input, info, chunk.seqNum); } else if (info.id == THIS_MASTER_NAME) { for (unsigned char i : info.stringData) { if (i != 0) { m_collector->designateMasterPage(chunk.seqNum); } } } else if (info.id == APPLIED_MASTER_NAME) { m_collector->setMasterPage(chunk.seqNum, info.data); } else { skipBlock(input, info); } } return true; } bool MSPUBParser::parsePageShapeList(librevenge::RVNGInputStream *input, MSPUBBlockInfo info, unsigned pageSeqNum) { MSPUB_DEBUG_MSG(("parsePageShapeList: page seqnum 0x%x\n", pageSeqNum)); while (stillReading(input, info.dataOffset + info.dataLength)) { MSPUBBlockInfo subInfo = parseBlock(input, true); if (subInfo.type == SHAPE_SEQNUM) { m_collector->setShapePage(subInfo.data, pageSeqNum); } } return true; } bool MSPUBParser::parseShape(librevenge::RVNGInputStream *input, const ContentChunkReference &chunk) { MSPUB_DEBUG_MSG(("parseShape: seqNum 0x%x\n", chunk.seqNum)); unsigned long pos = input->tell(); unsigned length = readU32(input); bool isTable = chunk.type == TABLE; bool isGroup = chunk.type == GROUP || chunk.type == LOGO; if (isTable) { boost::optional cellsSeqNum; boost::optional numRows; boost::optional numCols; boost::optional rowcolArrayOffset; boost::optional textId; while (stillReading(input, pos + length)) { MSPUBBlockInfo info = parseBlock(input, true); if (info.id == TABLE_CELLS_SEQNUM) { cellsSeqNum = info.data; } else if (info.id == TABLE_NUM_ROWS) { numRows = info.data; } else if (info.id == TABLE_NUM_COLS) { numCols = info.data; } else if (info.id == TABLE_ROWCOL_ARRAY) { rowcolArrayOffset = info.dataOffset; } else if (info.id == SHAPE_TEXT_ID) { textId = info.data; } } if (bool(cellsSeqNum) && bool(numRows) && bool(numCols) && bool(rowcolArrayOffset)) { unsigned nr = numRows.get(); unsigned nc = numCols.get(); unsigned rcao = rowcolArrayOffset.get(); unsigned csn = cellsSeqNum.get(); std::vector rowHeightsInEmu; std::vector columnWidthsInEmu; input->seek(rcao, librevenge::RVNG_SEEK_SET); unsigned arrayLength = readU32(input); while (stillReading(input, rcao + arrayLength)) { MSPUBBlockInfo info = parseBlock(input, true); if (info.id == 0) { input->seek(info.dataOffset + 4, librevenge::RVNG_SEEK_SET); while (stillReading(input, info.dataOffset + info.dataLength)) { MSPUBBlockInfo subInfo = parseBlock(input, true); if (subInfo.id == TABLE_ROWCOL_SIZE) { if (columnWidthsInEmu.size() < nc) columnWidthsInEmu.push_back(subInfo.data); else if (rowHeightsInEmu.size() < nr) rowHeightsInEmu.push_back(subInfo.data); } } } } if (rowHeightsInEmu.size() != nr || columnWidthsInEmu.size() != nc) { MSPUB_DEBUG_MSG(("ERROR: Wrong number of rows or columns found in table definition.\n")); return false; } boost::optional index; for (unsigned i = 0; i < m_cellsChunkIndices.size(); ++i) { if (m_contentChunks[m_cellsChunkIndices[i]].seqNum == csn) { index = i; break; } } TableInfo ti(nr, nc); ti.m_rowHeightsInEmu = rowHeightsInEmu; ti.m_columnWidthsInEmu = columnWidthsInEmu; if (!index) { MSPUB_DEBUG_MSG(("WARNING: Couldn't find cells of seqnum %u corresponding to table of seqnum %u.\n", csn, chunk.seqNum)); return false; } else { const ContentChunkReference &cellsChunk = m_contentChunks[m_cellsChunkIndices[get(index)]]; input->seek(cellsChunk.offset, librevenge::RVNG_SEEK_SET); const unsigned cellsLength = readU32(input); boost::optional cellCount; while (stillReading(input, cellsChunk.offset + cellsLength)) { MSPUBBlockInfo info = parseBlock(input, true); switch (info.id) { case 0x01: cellCount = info.data; break; case 0x02: { input->seek(info.dataOffset + 4, librevenge::RVNG_SEEK_SET); while (stillReading(input, info.dataOffset + info.dataLength)) { const MSPUBBlockInfo itemInfo = parseBlock(input, true); if (itemInfo.id == 0) { input->seek(itemInfo.dataOffset + 4, librevenge::RVNG_SEEK_SET); CellInfo currentCell; while (stillReading(input, itemInfo.dataOffset + itemInfo.dataLength)) { const MSPUBBlockInfo subInfo = parseBlock(input, true); switch (subInfo.id) { case 0x01: currentCell.m_startRow = subInfo.data; break; case 0x02: currentCell.m_endRow = subInfo.data; break; case 0x03: currentCell.m_startColumn = subInfo.data; break; case 0x04: currentCell.m_endColumn = subInfo.data; break; // TODO: 0x09 - 0x0e: width/height of content + margins? default: break; } } ti.m_cells.push_back(currentCell); } } break; } default: break; } } if (bool(cellCount) && (get(cellCount) != ti.m_cells.size())) { MSPUB_DEBUG_MSG(("%u cell records expected, but read %u\n", get(cellCount), unsigned(ti.m_cells.size()))); } } m_collector->setShapeTableInfo(chunk.seqNum, ti); if (bool(textId)) m_collector->addTextShape(get(textId), chunk.seqNum); return true; } return false; } else { bool isText = false; bool shouldStretchBorderArt = true; unsigned textId = 0; unsigned width = 0; unsigned height = 0; while (stillReading(input, pos + length)) { MSPUBBlockInfo info = parseBlock(input, true); if (info.id == SHAPE_WIDTH) { width = info.data; } else if (info.id == SHAPE_HEIGHT) { height = info.data; } else if (info.id == SHAPE_BORDER_IMAGE_ID) { m_collector->setShapeBorderImageId(chunk.seqNum, info.data); } else if (info.id == SHAPE_DONT_STRETCH_BA) { shouldStretchBorderArt = false; } else if (info.id == SHAPE_TEXT_ID) { textId = info.data; isText = true; } else if (info.id == SHAPE_VALIGN) { m_collector->setShapeVerticalTextAlign(chunk.seqNum, static_cast(info.data)); } else if (info.id == SHAPE_CROP && info.data != 0) { m_collector->setShapeCropType(chunk.seqNum, static_cast(info.data)); } } if (shouldStretchBorderArt) { m_collector->setShapeStretchBorderArt(chunk.seqNum); } bool parseWithoutDimensions = true; //FIXME: Should we ever ignore if height and width not given? if (isGroup || (height > 0 && width > 0) || parseWithoutDimensions) { if (! isGroup) { if (isText) { m_collector->addTextShape(textId, chunk.seqNum); } } } else { MSPUB_DEBUG_MSG(("Height and width not both specified, ignoring. (Height: 0x%x, Width: 0x%x)\n", height, width)); } return true; } } QuillChunkReference MSPUBParser::parseQuillChunkReference(librevenge::RVNGInputStream *input) { QuillChunkReference ret; readU16(input); //FIXME: Can we do something sensible if this is not 0x18 ? char name[5]; for (int i = 0; i < 4; ++i) { name[i] = (char)readU8(input); } name[4] = '\0'; ret.name = name; ret.id = readU16(input); input->seek(input->tell() + 4, librevenge::RVNG_SEEK_SET); //Seek past what is normally 0x01000000. We don't know what this represents. char name2[5]; for (int i = 0; i < 4; ++i) { name2[i] = (char)readU8(input); } name2[4] = '\0'; ret.name2 = name2; ret.offset = readU32(input); ret.length = readU32(input); return ret; } std::vector MSPUBParser::parseTableCellDefinitions( librevenge::RVNGInputStream *input, const QuillChunkReference &chunk) { std::vector ret; unsigned numElements = readU32(input) + 1; input->seek(chunk.offset + 0xC, librevenge::RVNG_SEEK_SET); for (unsigned i = 0; i < numElements; ++i) { ret.push_back(readU32(input)); // compensate for all but the last offset not including the terminating 0x0D00 if (i != numElements - 1) { ret.back() += 2; } } return ret; } bool MSPUBParser::parseQuill(librevenge::RVNGInputStream *input) { MSPUB_DEBUG_MSG(("MSPUBParser::parseQuill\n")); unsigned chunkReferenceListOffset = 0x18; std::list chunkReferences; std::set readChunks; // guard against cycle in the chunk list while (chunkReferenceListOffset != 0xffffffff) { input->seek(chunkReferenceListOffset + 2, librevenge::RVNG_SEEK_SET); unsigned short numChunks = readU16(input); chunkReferenceListOffset = readU32(input); if (readChunks.find(chunkReferenceListOffset) != readChunks.end()) { MSPUB_DEBUG_MSG(("Found a cycle in chunk reference list: a broken file!\n")); break; } readChunks.insert(chunkReferenceListOffset); for (unsigned i = 0; i < numChunks; ++i) { QuillChunkReference quillChunkReference = parseQuillChunkReference(input); chunkReferences.push_back(quillChunkReference); } } MSPUB_DEBUG_MSG(("Found %u Quill chunks\n", (unsigned)chunkReferences.size())); //Make sure we parse the STRS chunk before the TEXT chunk std::list::const_iterator textChunkReference = chunkReferences.end(); bool parsedStrs = false; bool parsedSyid = false; bool parsedFdpc = false; bool parsedFdpp = false; bool parsedStsh = false; bool parsedFont = false; std::vector textLengths; std::vector textIDs; std::vector textOffsets; std::map > tableCellTextEnds; unsigned textOffsetAccum = 0; std::vector spans; std::vector paras; unsigned whichStsh = 0; for (std::list::const_iterator i = chunkReferences.begin(); i != chunkReferences.end(); ++i) { if (i->name == "TEXT") { textChunkReference = i; } else if (i->name == "STRS") { input->seek(i->offset, librevenge::RVNG_SEEK_SET); unsigned numLengths = readU32(input); //Assuming the first DWORD is the number of children and that the next is the remaining length before children start. We are unsure that this is correct. input->seek(4 + i->offset + readU32(input), librevenge::RVNG_SEEK_SET); for (unsigned j = 0; j < numLengths; ++j) { unsigned length = readU32(input); textLengths.push_back(length); textOffsets.push_back(textOffsetAccum); textOffsetAccum += length * 2; } parsedStrs = true; } else if (i->name == "SYID") { input->seek(i->offset, librevenge::RVNG_SEEK_SET); readU32(input); // Don't know what the first DWORD means. unsigned numIDs = readU32(input); for (unsigned j = 0; j < numIDs; ++j) { textIDs.push_back(readU32(input)); } parsedSyid = true; } else if (i->name == "PL ") { input->seek(i->offset, librevenge::RVNG_SEEK_SET); parseColors(input, *i); } else if (i->name == "FDPC") { input->seek(i->offset, librevenge::RVNG_SEEK_SET); std::vector thisBlockSpans = parseCharacterStyles(input, *i); spans.insert(spans.end(), thisBlockSpans.begin(), thisBlockSpans.end()); parsedFdpc |= !thisBlockSpans.empty(); } else if (i->name == "FDPP") { input->seek(i->offset, librevenge::RVNG_SEEK_SET); std::vector thisBlockParas = parseParagraphStyles(input, *i); paras.insert(paras.end(), thisBlockParas.begin(), thisBlockParas.end()); parsedFdpp |= !thisBlockParas.empty(); } else if (i->name == "STSH") { if (whichStsh++ == 1) { input->seek(i->offset, librevenge::RVNG_SEEK_SET); parseDefaultStyle(input, *i); parsedStsh = true; } } else if (i->name == "FONT") { input->seek(i->offset, librevenge::RVNG_SEEK_SET); parseFonts(input, *i); parsedFont = true; } else if (i->name == "TCD ") { input->seek(i->offset, librevenge::RVNG_SEEK_SET); tableCellTextEnds[i->id] = parseTableCellDefinitions(input, *i); } } if (parsedStrs && parsedSyid && parsedFdpc && parsedFdpp && parsedStsh && parsedFont && textChunkReference != chunkReferences.end()) { input->seek(textChunkReference->offset, librevenge::RVNG_SEEK_SET); unsigned bytesRead = 0; auto currentTextSpan = spans.begin(); auto currentTextPara = paras.begin(); for (unsigned j = 0; j < textIDs.size() && j < textLengths.size(); ++j) { MSPUB_DEBUG_MSG(("Parsing a text block.\n")); std::vector readParas; std::vector readSpans; std::vector text; for (unsigned k = 0; k < textLengths[j] && currentTextPara != paras.end() && currentTextSpan != spans.end(); ++k) { text.push_back(readU8(input)); text.push_back(readU8(input)); bytesRead += 2; if (bytesRead >= currentTextSpan->last - textChunkReference->offset) { if (!text.empty()) { readSpans.push_back(TextSpan(text, currentTextSpan->charStyle)); MSPUB_DEBUG_MSG(("Saw text span %d in the current text paragraph.\n", (unsigned)readSpans.size())); } ++currentTextSpan; text.clear(); } if (bytesRead >= currentTextPara->last - textChunkReference->offset) { if (!text.empty()) { readSpans.push_back(TextSpan(text, currentTextSpan->charStyle)); MSPUB_DEBUG_MSG(("Saw text span %d in the current text paragraph.\n", (unsigned)readSpans.size())); } text.clear(); if (!readSpans.empty()) { readParas.push_back(TextParagraph(readSpans, currentTextPara->paraStyle)); MSPUB_DEBUG_MSG(("Saw paragraph %d in the current text block.\n", (unsigned)readParas.size())); } ++currentTextPara; readSpans.clear(); } } if (!text.empty() && currentTextSpan != spans.end()) { readSpans.push_back(TextSpan(text, currentTextSpan->charStyle)); MSPUB_DEBUG_MSG(("Saw text span %d in the current text paragraph.\n", (unsigned)readSpans.size())); } text.clear(); if (!readSpans.empty() && currentTextPara != paras.end()) { readParas.push_back(TextParagraph(readSpans, currentTextPara->paraStyle)); MSPUB_DEBUG_MSG(("Saw paragraph %d in the current text block.\n", (unsigned)readParas.size())); } m_collector->addTextString(readParas, textIDs[j]); m_collector->setTextStringOffset(textIDs[j], textOffsets[j]); const std::map >::const_iterator it = tableCellTextEnds.find(j); if (it != tableCellTextEnds.end()) m_collector->setTableCellTextEnds(textIDs[j], it->second); } textChunkReference = chunkReferences.end(); } return true; } void MSPUBParser::parseFonts(librevenge::RVNGInputStream *input, const QuillChunkReference &) { readU32(input); unsigned numElements = readU32(input); input->seek(input->tell() + 12 + 4 * numElements, librevenge::RVNG_SEEK_SET); for (unsigned i = 0; i < numElements; ++i) { unsigned short nameLength = readU16(input); if (nameLength > 0) { std::vector name; readNBytes(input, nameLength * 2, name); m_collector->addFont(name); } readU32(input); } } void MSPUBParser::parseDefaultStyle(librevenge::RVNGInputStream *input, const QuillChunkReference &chunk) { readU32(input); unsigned numElements = std::min(readU32(input), m_length); input->seek(input->tell() + 12, librevenge::RVNG_SEEK_SET); std::vector offsets; offsets.reserve(numElements); for (unsigned i = 0; i < numElements; ++i) { offsets.push_back(readU32(input)); } for (unsigned i = 0; i < numElements; ++i) { input->seek(chunk.offset + 20 + offsets[i], librevenge::RVNG_SEEK_SET); readU16(input); if (i % 2 == 0) { //FIXME: Does STSH2 hold information for associating style indices in FDPP to indices in STSH1 ? m_collector->addDefaultCharacterStyle(getCharacterStyle(input)); } else { m_collector->addDefaultParagraphStyle(getParagraphStyle(input)); } } } void MSPUBParser::parseColors(librevenge::RVNGInputStream *input, const QuillChunkReference &) { unsigned numEntries = readU32(input); input->seek(input->tell() + 8, librevenge::RVNG_SEEK_SET); for (unsigned i = 0; i < numEntries; ++i) { unsigned blocksOffset = input->tell(); unsigned len = readU32(input); while (stillReading(input, blocksOffset + len)) { MSPUBBlockInfo info = parseBlock(input, true); if (info.id == 0x01) { m_collector->addTextColor(ColorReference(info.data)); } } } } std::vector MSPUBParser::parseParagraphStyles(librevenge::RVNGInputStream *input, const QuillChunkReference &chunk) { std::vector ret; unsigned short numEntries = readU16(input); input->seek(input->tell() + 6, librevenge::RVNG_SEEK_SET); std::vector textOffsets; textOffsets.reserve(numEntries); std::vector chunkOffsets; textOffsets.reserve(numEntries); for (unsigned short i = 0; i < numEntries; ++i) { textOffsets.push_back(readU32(input)); } for (unsigned short i = 0; i < numEntries; ++i) { chunkOffsets.push_back(readU16(input)); } unsigned currentSpanBegin = 0; for (unsigned short i = 0; i < numEntries; ++i) { input->seek(chunk.offset + chunkOffsets[i], librevenge::RVNG_SEEK_SET); ParagraphStyle style = getParagraphStyle(input); ret.push_back(TextParagraphReference(currentSpanBegin, textOffsets[i], style)); currentSpanBegin = textOffsets[i] + 1; } return ret; } std::vector MSPUBParser::parseCharacterStyles(librevenge::RVNGInputStream *input, const QuillChunkReference &chunk) { unsigned short numEntries = readU16(input); input->seek(input->tell() + 6, librevenge::RVNG_SEEK_SET); std::vector textOffsets; textOffsets.reserve(numEntries); std::vector chunkOffsets; chunkOffsets.reserve(numEntries); std::vector ret; for (unsigned short i = 0; i < numEntries; ++i) { textOffsets.push_back(readU32(input)); } for (unsigned short i = 0; i < numEntries; ++i) { chunkOffsets.push_back(readU16(input)); } unsigned currentSpanBegin = 0; for (unsigned short i = 0; i < numEntries; ++i) { input->seek(chunk.offset + chunkOffsets[i], librevenge::RVNG_SEEK_SET); CharacterStyle style = getCharacterStyle(input); currentSpanBegin = textOffsets[i] + 1; ret.push_back(TextSpanReference(currentSpanBegin, textOffsets[i], style)); } return ret; } ParagraphStyle MSPUBParser::getParagraphStyle(librevenge::RVNGInputStream *input) { ParagraphStyle ret; bool isList = false; unsigned bulletChar = 0; NumberingType numberingType = STANDARD_WESTERN; NumberingDelimiter numberingDelimiter = NO_DELIMITER; boost::optional numberIfRestarted; unsigned offset = input->tell(); unsigned len = readU32(input); while (stillReading(input, offset + len)) { MSPUBBlockInfo info = parseBlock(input, true); switch (info.id) { case PARAGRAPH_ALIGNMENT: ret.m_align = (Alignment)(info.data & 0xFF); // Is this correct? break; case PARAGRAPH_DEFAULT_CHAR_STYLE: ret.m_defaultCharStyleIndex = info.data; break; case PARAGRAPH_LINE_SPACING: if (info.data & 1) { // line spacing expressed in points in the UI, // in eighths of an emu in the file format. // (WTF??) ret.m_lineSpacing = LineSpacingInfo(LINE_SPACING_PT, static_cast(info.data - 1) / 8 * 72 / EMUS_IN_INCH); } else if (info.data & 2) { // line spacing expressed in SP in the UI, // in what would be EMUs if font size were 96pt in the file format // (WTF??) ret.m_lineSpacing = LineSpacingInfo(LINE_SPACING_SP, static_cast(info.data - 2) / EMUS_IN_INCH * 72 / 96); } break; case PARAGRAPH_SPACE_BEFORE: ret.m_spaceBeforeEmu = info.data; break; case PARAGRAPH_SPACE_AFTER: ret.m_spaceAfterEmu = info.data; break; case PARAGRAPH_FIRST_LINE_INDENT: ret.m_firstLineIndentEmu = (int)info.data; break; case PARAGRAPH_LEFT_INDENT: ret.m_leftIndentEmu = info.data; break; case PARAGRAPH_RIGHT_INDENT: ret.m_rightIndentEmu = info.data; break; case PARAGRAPH_TABS: input->seek(info.dataOffset + 4, librevenge::RVNG_SEEK_SET); while (stillReading(input, info.dataOffset + info.dataLength)) { MSPUBBlockInfo tabArrayInfo = parseBlock(input, true); if (tabArrayInfo.id == TAB_ARRAY) { input->seek(tabArrayInfo.dataOffset + 4, librevenge::RVNG_SEEK_SET); while (stillReading(input, tabArrayInfo.dataOffset + tabArrayInfo.dataLength)) { MSPUBBlockInfo tabEntryInfo = parseBlock(input, true); if (tabEntryInfo.type == GENERAL_CONTAINER) { input->seek(tabEntryInfo.dataOffset + 4, librevenge::RVNG_SEEK_SET); MSPUBBlockInfo tabInfo = parseBlock(input, true); if (tabInfo.id == TAB_AMOUNT) { ret.m_tabStopsInEmu.push_back(tabInfo.data); } } } } } break; case PARAGRAPH_LIST_INFO: { isList = true; input->seek(info.dataOffset + 4, librevenge::RVNG_SEEK_SET); while (stillReading(input, info.dataOffset + info.dataLength)) { MSPUBBlockInfo listSubInfo = parseBlock(input, true); switch (listSubInfo.id) { case PARAGRAPH_LIST_NUMBERING_TYPE: numberingType = static_cast(info.data); break; case PARAGRAPH_LIST_BULLET_CHAR: bulletChar = info.data; break; default: break; } } break; } case PARAGRAPH_LIST_NUMBER_RESTART: numberIfRestarted = info.data; break; case PARAGRAPH_DROP_CAP_LINES: ret.m_dropCapLines = info.data; break; case PARAGRAPH_DROP_CAP_LETTERS: ret.m_dropCapLetters = info.data; break; default: break; } } if (isList) { if (bulletChar) { ret.m_listInfo = ListInfo(bulletChar); } else { ret.m_listInfo = ListInfo(numberIfRestarted, numberingType, numberingDelimiter); } } return ret; } CharacterStyle MSPUBParser::getCharacterStyle(librevenge::RVNGInputStream *input) { CharacterStyle style; bool seenBold1 = false, seenBold2 = false, seenItalic1 = false, seenItalic2 = false; int textSize1 = -1, /* textSize2 = -1,*/ colorIndex = -1; boost::optional fontIndex; unsigned offset = input->tell(); unsigned len = readU32(input); while (stillReading(input, offset + len)) { MSPUBBlockInfo info = parseBlock(input, true); switch (info.id) { case BOLD_1_ID: seenBold1 = true; break; case BOLD_2_ID: seenBold2 = true; break; case ITALIC_1_ID: seenItalic1 = true; break; case ITALIC_2_ID: seenItalic2 = true; break; case UNDERLINE_ID: style.underline = readUnderline(info.data); break; case TEXT_SIZE_1_ID: textSize1 = info.data; break; case TEXT_SIZE_2_ID: // textSize2 = info.data; break; case BARE_COLOR_INDEX_ID: colorIndex = info.data; break; case COLOR_INDEX_CONTAINER_ID: colorIndex = getColorIndex(input, info); break; case FONT_INDEX_CONTAINER_ID: fontIndex = getFontIndex(input, info); break; case SUPER_SUB_TYPE_ID: style.superSubType = static_cast(info.data); break; case OUTLINE_ID: style.outline = true; break; case SHADOW_ID: style.shadow = true; break; case SMALL_CAPS_ID: style.smallCaps = true; break; case ALL_CAPS_ID: style.allCaps = true; break; case EMBOSS_ID: style.emboss = true; break; case ENGRAVE_ID: style.engrave = true; break; case SCALING_ID: style.textScale = double(info.data) / 10; break; case LOCALE_ID: style.lcid = info.data; break; default: break; } } //FIXME: Figure out what textSize2 is used for. Can we find a document where it differs from textSize1 ? // textSize2 = textSize1; boost::optional dTextSize; if (textSize1 != -1) { dTextSize = textSize1 * (double(POINTS_IN_INCH) / EMUS_IN_INCH); } // FIXME: What's this with foo1 && foo2? I've only seen foo1 in 2k2 // files and either just foo1 or foo1+foo2 in 2k7 files... style.italic = seenItalic1; // && seenItalic2; style.bold = seenBold1; // && seenBold2; (void) seenItalic2; (void) seenBold2; style.textSizeInPt = dTextSize; style.colorIndex = getColorIndexByQuillEntry(colorIndex); style.fontIndex = fontIndex; return style; } unsigned MSPUBParser::getFontIndex(librevenge::RVNGInputStream *input, const MSPUBBlockInfo &info) { MSPUB_DEBUG_MSG(("In getFontIndex\n")); input->seek(info.dataOffset + 4, librevenge::RVNG_SEEK_SET); while (stillReading(input, info.dataOffset + info.dataLength)) { MSPUBBlockInfo subInfo = parseBlock(input, true); if (subInfo.type == GENERAL_CONTAINER) { input->seek(subInfo.dataOffset + 4, librevenge::RVNG_SEEK_SET); if (stillReading(input, subInfo.dataOffset + subInfo.dataLength)) { MSPUBBlockInfo subSubInfo = parseBlock(input, true); skipBlock(input, info); return subSubInfo.data; } } } return 0; } int MSPUBParser::getColorIndex(librevenge::RVNGInputStream *input, const MSPUBBlockInfo &info) { input->seek(info.dataOffset + 4, librevenge::RVNG_SEEK_SET); while (stillReading(input, info.dataOffset + info.dataLength)) { MSPUBBlockInfo subInfo = parseBlock(input, true); if (subInfo.id == COLOR_INDEX_ID) { skipBlock(input, info); MSPUB_DEBUG_MSG(("Found color index 0x%x\n", (unsigned)subInfo.data)); return subInfo.data; } } MSPUB_DEBUG_MSG(("Failed to find color index!\n")); return -1; } bool MSPUBParser::parseEscher(librevenge::RVNGInputStream *input) { MSPUB_DEBUG_MSG(("MSPUBParser::parseEscher\n")); EscherContainerInfo fakeroot; fakeroot.initial = 0; fakeroot.type = 0; fakeroot.contentsOffset = input->tell(); fakeroot.contentsLength = (unsigned long)-1; //FIXME: Get the actual length EscherContainerInfo dg, dgg; //Note: this assumes that dgg comes before any dg with images. if (findEscherContainer(input, fakeroot, dgg, OFFICE_ART_DGG_CONTAINER)) { EscherContainerInfo bsc; if (findEscherContainer(input, fakeroot, bsc, OFFICE_ART_B_STORE_CONTAINER)) { unsigned short currentDelayIndex = 1; while (stillReading(input, bsc.contentsOffset + bsc.contentsLength)) { unsigned begin = input->tell(); input->seek(begin + 10, librevenge::RVNG_SEEK_SET); if (!(readU32(input) == 0 && readU32(input) == 0 && readU32(input) == 0 && readU32(input) == 0)) { m_escherDelayIndices.push_back(currentDelayIndex++); } else { m_escherDelayIndices.push_back(-1); } input->seek(begin + 44, librevenge::RVNG_SEEK_SET); } } input->seek(dgg.contentsOffset + dgg.contentsLength + getEscherElementTailLength(OFFICE_ART_DGG_CONTAINER), librevenge::RVNG_SEEK_SET); } while (findEscherContainer(input, fakeroot, dg, OFFICE_ART_DG_CONTAINER)) { EscherContainerInfo spgr; while (findEscherContainer(input, dg, spgr, OFFICE_ART_SPGR_CONTAINER)) { Coordinate c1, c2; parseShapeGroup(input, spgr, c1, c2); } input->seek(input->tell() + getEscherElementTailLength(OFFICE_ART_DG_CONTAINER), librevenge::RVNG_SEEK_SET); } return true; } void MSPUBParser::parseShapeGroup(librevenge::RVNGInputStream *input, const EscherContainerInfo &spgr, Coordinate parentCoordinateSystem, Coordinate parentGroupAbsoluteCoord) { EscherContainerInfo shapeOrGroup; std::set types; types.insert(OFFICE_ART_SPGR_CONTAINER); types.insert(OFFICE_ART_SP_CONTAINER); while (findEscherContainerWithTypeInSet(input, spgr, shapeOrGroup, types)) { switch (shapeOrGroup.type) { case OFFICE_ART_SPGR_CONTAINER: m_collector->beginGroup(); parseShapeGroup(input, shapeOrGroup, parentCoordinateSystem, parentGroupAbsoluteCoord); m_collector->endGroup(); break; case OFFICE_ART_SP_CONTAINER: parseEscherShape(input, shapeOrGroup, parentCoordinateSystem, parentGroupAbsoluteCoord); break; } input->seek(shapeOrGroup.contentsOffset + shapeOrGroup.contentsLength + getEscherElementTailLength(shapeOrGroup.type), librevenge::RVNG_SEEK_SET); } } void MSPUBParser::parseEscherShape(librevenge::RVNGInputStream *input, const EscherContainerInfo &sp, Coordinate &parentCoordinateSystem, Coordinate &parentGroupAbsoluteCoord) { Coordinate thisParentCoordinateSystem = parentCoordinateSystem; bool definesRelativeCoordinates = false; EscherContainerInfo cData; EscherContainerInfo cAnchor; EscherContainerInfo cFopt; EscherContainerInfo cTertiaryFopt; EscherContainerInfo cFsp; EscherContainerInfo cFspgr; unsigned shapeFlags = 0; bool isGroupLeader = false; ShapeType st = RECTANGLE; if (findEscherContainer(input, sp, cFspgr, OFFICE_ART_FSPGR)) { input->seek(cFspgr.contentsOffset, librevenge::RVNG_SEEK_SET); parentCoordinateSystem.m_xs = readU32(input); parentCoordinateSystem.m_ys = readU32(input); parentCoordinateSystem.m_xe = readU32(input); parentCoordinateSystem.m_ye = readU32(input); definesRelativeCoordinates = true; } input->seek(sp.contentsOffset, librevenge::RVNG_SEEK_SET); if (findEscherContainer(input, sp, cFsp, OFFICE_ART_FSP)) { st = (ShapeType)(cFsp.initial >> 4); std::map fspData = extractEscherValues(input, cFsp); input->seek(cFsp.contentsOffset + 4, librevenge::RVNG_SEEK_SET); shapeFlags = readU32(input); isGroupLeader = shapeFlags & SF_GROUP; } input->seek(sp.contentsOffset, librevenge::RVNG_SEEK_SET); if (findEscherContainer(input, sp, cData, OFFICE_ART_CLIENT_DATA)) { std::map dataValues = extractEscherValues(input, cData); unsigned *shapeSeqNum = getIfExists(dataValues, FIELDID_SHAPE_ID); if (shapeSeqNum) { m_collector->setShapeType(*shapeSeqNum, st); m_collector->setShapeFlip(*shapeSeqNum, shapeFlags & SF_FLIP_V, shapeFlags & SF_FLIP_H); input->seek(sp.contentsOffset, librevenge::RVNG_SEEK_SET); if (isGroupLeader) { m_collector->setCurrentGroupSeqNum(*shapeSeqNum); } else { m_collector->setShapeOrder(*shapeSeqNum); } std::set anchorTypes; anchorTypes.insert(OFFICE_ART_CLIENT_ANCHOR); anchorTypes.insert(OFFICE_ART_CHILD_ANCHOR); bool foundAnchor; if ((foundAnchor = findEscherContainerWithTypeInSet(input, sp, cAnchor, anchorTypes)) || isGroupLeader) { bool rotated90 = false; MSPUB_DEBUG_MSG(("Found Escher data for %s of seqnum 0x%x\n", isGroupLeader ? "group" : "shape", *shapeSeqNum)); boost::optional > maybe_tertiaryFoptValues; input->seek(sp.contentsOffset, librevenge::RVNG_SEEK_SET); if (findEscherContainer(input, sp, cTertiaryFopt, OFFICE_ART_TERTIARY_FOPT)) { maybe_tertiaryFoptValues = extractEscherValues(input, cTertiaryFopt); } if (bool(maybe_tertiaryFoptValues)) { const std::map &tertiaryFoptValues = maybe_tertiaryFoptValues.get(); const unsigned *ptr_pictureRecolor = getIfExists_const(tertiaryFoptValues, FIELDID_PICTURE_RECOLOR); if (ptr_pictureRecolor) { m_collector->setShapePictureRecolor(*shapeSeqNum, ColorReference(*ptr_pictureRecolor)); } } input->seek(sp.contentsOffset, librevenge::RVNG_SEEK_SET); if (findEscherContainer(input, sp, cFopt, OFFICE_ART_FOPT)) { FOPTValues foptValues = extractFOPTValues(input, cFopt); unsigned *pxId = getIfExists(foptValues.m_scalarValues, FIELDID_PXID); if (pxId) { MSPUB_DEBUG_MSG(("Current Escher shape has pxId %d\n", *pxId)); if (*pxId > 0 && *pxId <= m_escherDelayIndices.size() && m_escherDelayIndices[*pxId - 1] >= 0) { m_collector->setShapeImgIndex(*shapeSeqNum, m_escherDelayIndices[*pxId - 1]); } else { MSPUB_DEBUG_MSG(("Couldn't find corresponding escherDelay index\n")); } unsigned *ptr_pictureBrightness = getIfExists(foptValues.m_scalarValues, FIELDID_PICTURE_BRIGHTNESS); if (ptr_pictureBrightness) { m_collector->setShapePictureBrightness(*shapeSeqNum, (int)(*ptr_pictureBrightness)); } unsigned *ptr_pictureContrast = getIfExists(foptValues.m_scalarValues, FIELDID_PICTURE_CONTRAST); if (ptr_pictureContrast) { m_collector->setShapePictureContrast(*shapeSeqNum, (int)(*ptr_pictureContrast)); } } unsigned *ptr_lineBackColor = getIfExists(foptValues.m_scalarValues, FIELDID_LINE_BACK_COLOR); if (ptr_lineBackColor && static_cast(*ptr_lineBackColor) != -1) { m_collector->setShapeLineBackColor( *shapeSeqNum, ColorReference(*ptr_lineBackColor)); } unsigned *ptr_lineColor = getIfExists(foptValues.m_scalarValues, FIELDID_LINE_COLOR); unsigned *ptr_lineFlags = getIfExists(foptValues.m_scalarValues, FIELDID_LINE_STYLE_BOOL_PROPS); unsigned *ptr_geomFlags = getIfExists( foptValues.m_scalarValues, FIELDID_GEOM_BOOL_PROPS); bool useLine = lineExistsByFlagPointer( ptr_lineFlags, ptr_geomFlags); bool skipIfNotBg = false; std::shared_ptr ptr_fill = getNewFill(foptValues.m_scalarValues, skipIfNotBg, foptValues.m_complexValues); unsigned lineWidth = 0; if (useLine) { if (ptr_lineColor) { unsigned *ptr_lineWidth = getIfExists(foptValues.m_scalarValues, FIELDID_LINE_WIDTH); lineWidth = ptr_lineWidth ? *ptr_lineWidth : 9525; m_collector->addShapeLine(*shapeSeqNum, Line(ColorReference(*ptr_lineColor), lineWidth, true)); } else { if (bool(maybe_tertiaryFoptValues)) { std::map &tertiaryFoptValues = maybe_tertiaryFoptValues.get(); unsigned *ptr_tertiaryLineFlags = getIfExists(tertiaryFoptValues, FIELDID_LINE_STYLE_BOOL_PROPS); if (lineExistsByFlagPointer(ptr_tertiaryLineFlags)) { unsigned *ptr_topColor = getIfExists(tertiaryFoptValues, FIELDID_LINE_TOP_COLOR); unsigned *ptr_topWidth = getIfExists(tertiaryFoptValues, FIELDID_LINE_TOP_WIDTH); unsigned *ptr_topFlags = getIfExists(tertiaryFoptValues, FIELDID_LINE_TOP_BOOL_PROPS); unsigned *ptr_rightColor = getIfExists(tertiaryFoptValues, FIELDID_LINE_RIGHT_COLOR); unsigned *ptr_rightWidth = getIfExists(tertiaryFoptValues, FIELDID_LINE_RIGHT_WIDTH); unsigned *ptr_rightFlags = getIfExists(tertiaryFoptValues, FIELDID_LINE_RIGHT_BOOL_PROPS); unsigned *ptr_bottomColor = getIfExists(tertiaryFoptValues, FIELDID_LINE_BOTTOM_COLOR); unsigned *ptr_bottomWidth = getIfExists(tertiaryFoptValues, FIELDID_LINE_BOTTOM_WIDTH); unsigned *ptr_bottomFlags = getIfExists(tertiaryFoptValues, FIELDID_LINE_BOTTOM_BOOL_PROPS); unsigned *ptr_leftColor = getIfExists(tertiaryFoptValues, FIELDID_LINE_LEFT_COLOR); unsigned *ptr_leftWidth = getIfExists(tertiaryFoptValues, FIELDID_LINE_LEFT_WIDTH); unsigned *ptr_leftFlags = getIfExists(tertiaryFoptValues, FIELDID_LINE_LEFT_BOOL_PROPS); bool topExists = ptr_topColor && lineExistsByFlagPointer(ptr_topFlags); bool rightExists = ptr_rightColor && lineExistsByFlagPointer(ptr_rightFlags); bool bottomExists = ptr_bottomColor && lineExistsByFlagPointer(ptr_bottomFlags); bool leftExists = ptr_leftColor && lineExistsByFlagPointer(ptr_leftFlags); if (ptr_topWidth) { lineWidth = *ptr_topWidth; } m_collector->addShapeLine(*shapeSeqNum, topExists ? Line(ColorReference(*ptr_topColor), ptr_topWidth ? *ptr_topWidth : 9525, true) : Line(ColorReference(0), 0, false)); m_collector->addShapeLine(*shapeSeqNum, rightExists ? Line(ColorReference(*ptr_rightColor), ptr_rightWidth ? *ptr_rightWidth : 9525, true) : Line(ColorReference(0), 0, false)); m_collector->addShapeLine(*shapeSeqNum, bottomExists ? Line(ColorReference(*ptr_bottomColor), ptr_bottomWidth ? *ptr_bottomWidth : 9525, true) : Line(ColorReference(0), 0, false)); m_collector->addShapeLine(*shapeSeqNum, leftExists ? Line(ColorReference(*ptr_leftColor), ptr_leftWidth ? *ptr_leftWidth : 9525, true) : Line(ColorReference(0), 0, false)); // Amazing feat of Microsoft engineering: // The detailed interaction of four flags describes ONE true/false property! if (ptr_leftFlags && (*ptr_leftFlags & FLAG_USE_LEFT_INSET_PEN) && (!(*ptr_leftFlags & FLAG_USE_LEFT_INSET_PEN_OK) || (*ptr_leftFlags & FLAG_LEFT_INSET_PEN_OK)) && (*ptr_leftFlags & FLAG_LEFT_INSET_PEN)) { m_collector->setShapeBorderPosition(*shapeSeqNum, INSIDE_SHAPE); } else { m_collector->setShapeBorderPosition(*shapeSeqNum, HALF_INSIDE_SHAPE); } } } } } if (ptr_fill) { m_collector->setShapeFill(*shapeSeqNum, ptr_fill, skipIfNotBg); } int *ptr_adjust1 = (int *)getIfExists(foptValues.m_scalarValues, FIELDID_ADJUST_VALUE_1); int *ptr_adjust2 = (int *)getIfExists(foptValues.m_scalarValues, FIELDID_ADJUST_VALUE_2); int *ptr_adjust3 = (int *)getIfExists(foptValues.m_scalarValues, FIELDID_ADJUST_VALUE_3); if (ptr_adjust1) { m_collector->setAdjustValue(*shapeSeqNum, 0, *ptr_adjust1); } if (ptr_adjust2) { m_collector->setAdjustValue(*shapeSeqNum, 1, *ptr_adjust2); } if (ptr_adjust3) { m_collector->setAdjustValue(*shapeSeqNum, 2, *ptr_adjust3); } int *ptr_rotation = (int *)getIfExists(foptValues.m_scalarValues, FIELDID_ROTATION); if (ptr_rotation) { double rotation = doubleModulo(toFixedPoint(*ptr_rotation), 360); m_collector->setShapeRotation(*shapeSeqNum, short(rotation)); //FIXME : make MSPUBCollector handle double shape rotations rotated90 = (rotation >= 45 && rotation < 135) || (rotation >= 225 && rotation < 315); } unsigned *ptr_left = getIfExists(foptValues.m_scalarValues, FIELDID_DY_TEXT_LEFT); unsigned *ptr_top = getIfExists(foptValues.m_scalarValues, FIELDID_DY_TEXT_TOP); unsigned *ptr_right = getIfExists(foptValues.m_scalarValues, FIELDID_DY_TEXT_RIGHT); unsigned *ptr_bottom = getIfExists(foptValues.m_scalarValues, FIELDID_DY_TEXT_BOTTOM); m_collector->setShapeMargins(*shapeSeqNum, ptr_left ? *ptr_left : DEFAULT_MARGIN, ptr_top ? *ptr_top : DEFAULT_MARGIN, ptr_right ? *ptr_right : DEFAULT_MARGIN, ptr_bottom ? *ptr_bottom : DEFAULT_MARGIN); unsigned *ptr_lineDashing = getIfExists(foptValues.m_scalarValues, FIELDID_LINE_DASHING); unsigned *ptr_lineEndcapStyle = getIfExists(foptValues.m_scalarValues, FIELDID_LINE_ENDCAP_STYLE); DotStyle dotStyle = RECT_DOT; if (ptr_lineEndcapStyle) { switch (*ptr_lineEndcapStyle) { case 0: dotStyle = ROUND_DOT; break; default: break; } } if (ptr_lineDashing) { m_collector->setShapeDash(*shapeSeqNum, getDash( static_cast(*ptr_lineDashing), lineWidth, dotStyle)); } if (bool(maybe_tertiaryFoptValues)) { std::map &tertiaryFoptValues = maybe_tertiaryFoptValues.get(); unsigned *ptr_numColumns = getIfExists(tertiaryFoptValues, FIELDID_NUM_COLUMNS); if (ptr_numColumns) { m_collector->setShapeNumColumns(*shapeSeqNum, *ptr_numColumns); } unsigned *ptr_columnSpacing = getIfExists(tertiaryFoptValues, FIELDID_COLUMN_SPACING); if (ptr_columnSpacing) { m_collector->setShapeColumnSpacing(*shapeSeqNum, *ptr_columnSpacing); } } unsigned *ptr_beginArrowStyle = getIfExists(foptValues.m_scalarValues, FIELDID_BEGIN_ARROW_STYLE); unsigned *ptr_beginArrowWidth = getIfExists(foptValues.m_scalarValues, FIELDID_BEGIN_ARROW_WIDTH); unsigned *ptr_beginArrowHeight = getIfExists(foptValues.m_scalarValues, FIELDID_BEGIN_ARROW_HEIGHT); m_collector->setShapeBeginArrow(*shapeSeqNum, Arrow( ptr_beginArrowStyle ? (ArrowStyle)(*ptr_beginArrowStyle) : NO_ARROW, ptr_beginArrowWidth ? (ArrowSize)(*ptr_beginArrowWidth) : MEDIUM, ptr_beginArrowHeight ? (ArrowSize)(*ptr_beginArrowHeight) : MEDIUM)); unsigned *ptr_endArrowStyle = getIfExists(foptValues.m_scalarValues, FIELDID_END_ARROW_STYLE); unsigned *ptr_endArrowWidth = getIfExists(foptValues.m_scalarValues, FIELDID_END_ARROW_WIDTH); unsigned *ptr_endArrowHeight = getIfExists(foptValues.m_scalarValues, FIELDID_END_ARROW_HEIGHT); m_collector->setShapeEndArrow(*shapeSeqNum, Arrow( ptr_endArrowStyle ? (ArrowStyle)(*ptr_endArrowStyle) : NO_ARROW, ptr_endArrowWidth ? (ArrowSize)(*ptr_endArrowWidth) : MEDIUM, ptr_endArrowHeight ? (ArrowSize)(*ptr_endArrowHeight) : MEDIUM)); unsigned *shadowBoolProps = getIfExists(foptValues.m_scalarValues, FIELDID_SHADOW_BOOL_PROPS); if (shadowBoolProps) { unsigned shadowProps = *shadowBoolProps; if ((shadowProps & FLAG_USE_FSHADOW) && (shadowProps & FLAG_USE_SHADOW)) { unsigned *ptr_shadowType = getIfExists(foptValues.m_scalarValues, FIELDID_SHADOW_TYPE); auto shadowType = static_cast(ptr_shadowType ? *ptr_shadowType : 0); unsigned *shadowColor = getIfExists(foptValues.m_scalarValues, FIELDID_SHADOW_COLOR); unsigned *shadowHColor = getIfExists(foptValues.m_scalarValues, FIELDID_SHADOW_HIGHLIGHT); unsigned *shadowOpacity = getIfExists(foptValues.m_scalarValues, FIELDID_SHADOW_OPACITY); unsigned *shadowOffsetX = getIfExists(foptValues.m_scalarValues, FIELDID_SHADOW_OFFSET_X); unsigned *shadowOffsetY = getIfExists(foptValues.m_scalarValues, FIELDID_SHADOW_OFFSET_Y); unsigned *shadowOffsetX2 = getIfExists(foptValues.m_scalarValues, FIELDID_SHADOW_SECOND_OFFSET_X); unsigned *shadowOffsetY2 = getIfExists(foptValues.m_scalarValues, FIELDID_SHADOW_SECOND_OFFSET_Y); unsigned *shadowOriginX = getIfExists(foptValues.m_scalarValues, FIELDID_SHADOW_ORIGIN_X); unsigned *shadowOriginY = getIfExists(foptValues.m_scalarValues, FIELDID_SHADOW_ORIGIN_Y); m_collector->setShapeShadow(*shapeSeqNum, Shadow(shadowType, shadowOffsetX ? static_cast(*shadowOffsetX) : 0x6338, shadowOffsetY ? static_cast(*shadowOffsetY) : 0x6338, shadowOffsetX2 ? static_cast(*shadowOffsetX2) : 0, shadowOffsetY2 ? static_cast(*shadowOffsetY2) : 0, shadowOriginX ? toFixedPoint(static_cast(*shadowOriginX)) : 0, shadowOriginY ? toFixedPoint(static_cast(*shadowOriginY)) : 0, toFixedPoint(shadowOpacity ? static_cast(*shadowOpacity) : 0x10000), ColorReference(shadowColor ? *shadowColor : 0x00808080), ColorReference(shadowHColor ? *shadowHColor : 0x00CBCBCB) )); } } const std::vector vertexData = foptValues.m_complexValues[FIELDID_P_VERTICES]; if (!vertexData.empty()) { unsigned *p_geoRight = getIfExists(foptValues.m_scalarValues, FIELDID_GEO_RIGHT); unsigned *p_geoBottom = getIfExists(foptValues.m_scalarValues, FIELDID_GEO_BOTTOM); const std::vector segmentData = foptValues.m_complexValues[FIELDID_P_SEGMENTS]; const std::vector guideData = foptValues.m_complexValues[FIELDID_P_GUIDES]; m_collector->setShapeCustomPath(*shapeSeqNum, getDynamicCustomShape(vertexData, segmentData, guideData, p_geoRight ? *p_geoRight : 21600, p_geoBottom ? *p_geoBottom : 21600)); } const std::vector wrapVertexData = foptValues.m_complexValues[FIELDID_P_WRAPPOLYGONVERTICES]; if (!wrapVertexData.empty()) { std::vector ret = parseVertices(wrapVertexData); m_collector->setShapeClipPath(*shapeSeqNum, ret); } } if (foundAnchor) { Coordinate absolute; if (cAnchor.type == OFFICE_ART_CLIENT_ANCHOR) { std::map anchorData = extractEscherValues(input, cAnchor); absolute = Coordinate(anchorData[FIELDID_XS], anchorData[FIELDID_YS], anchorData[FIELDID_XE], anchorData[FIELDID_YE]); } else if (cAnchor.type == OFFICE_ART_CHILD_ANCHOR) { input->seek(cAnchor.contentsOffset, librevenge::RVNG_SEEK_SET); int coordSystemWidth = int64_t(thisParentCoordinateSystem.m_xe) - thisParentCoordinateSystem.m_xs; if (coordSystemWidth == 0) coordSystemWidth = 1; int coordSystemHeight = thisParentCoordinateSystem.m_ye - thisParentCoordinateSystem.m_ys; if (coordSystemHeight == 0) coordSystemHeight = 1; int groupWidth = int64_t(parentGroupAbsoluteCoord.m_xe) - parentGroupAbsoluteCoord.m_xs; int groupHeight = int64_t(parentGroupAbsoluteCoord.m_ye) - parentGroupAbsoluteCoord.m_ys; double widthScale = (double)groupWidth / coordSystemWidth; double heightScale = (double)groupHeight / coordSystemHeight; int xs = (readU32(input) - thisParentCoordinateSystem.m_xs) * widthScale + parentGroupAbsoluteCoord.m_xs; int ys = (readU32(input) - thisParentCoordinateSystem.m_ys) * heightScale + parentGroupAbsoluteCoord.m_ys; int xe = (readU32(input) - thisParentCoordinateSystem.m_xs) * widthScale + parentGroupAbsoluteCoord.m_xs; int ye = (readU32(input) - thisParentCoordinateSystem.m_ys) * heightScale + parentGroupAbsoluteCoord.m_ys; absolute = Coordinate(xs, ys, xe, ye); } if (rotated90) { int initialWidth = int64_t(absolute.m_xe) - absolute.m_xs; int initialHeight = int64_t(absolute.m_ye) - absolute.m_ys; int centerX = int64_t(absolute.m_xs) + initialWidth / 2; int centerY = int64_t(absolute.m_ys) + initialHeight / 2; int xs = centerX - initialHeight / 2; int ys = centerY - initialWidth / 2; int xe = xs + initialHeight; int ye = ys + initialWidth; absolute = Coordinate(xs, ys, xe, ye); } m_collector->setShapeCoordinatesInEmu(*shapeSeqNum, absolute.m_xs, absolute.m_ys, absolute.m_xe, absolute.m_ye); if (definesRelativeCoordinates) { parentGroupAbsoluteCoord = absolute; } } } } } } std::shared_ptr MSPUBParser::getNewFill(const std::map &foptProperties, bool &skipIfNotBg, std::map > &foptValues) { const FillType *ptr_fillType = (FillType *)getIfExists_const(foptProperties, FIELDID_FILL_TYPE); FillType fillType = ptr_fillType ? *ptr_fillType : SOLID; switch (fillType) { case SOLID: { const unsigned *ptr_fillColor = getIfExists_const(foptProperties, FIELDID_FILL_COLOR); const unsigned *ptr_fieldStyleProps = getIfExists_const(foptProperties, FIELDID_FIELD_STYLE_BOOL_PROPS); skipIfNotBg = ptr_fieldStyleProps && (*ptr_fieldStyleProps & 0xF0) == 0; if (ptr_fillColor && !skipIfNotBg) { const unsigned *ptr_fillOpacity = getIfExists_const(foptProperties, FIELDID_FILL_OPACITY); return std::shared_ptr(new SolidFill(ColorReference(*ptr_fillColor), ptr_fillOpacity ? (double)(*ptr_fillOpacity) / 0xFFFF : 1, m_collector)); } return std::shared_ptr(); } case SHADE_SHAPE: case SHADE_CENTER: case SHADE: case SHADE_SCALE: { int angle; const int *ptr_angle = (const int *)getIfExists_const(foptProperties, FIELDID_FILL_ANGLE); const unsigned *ptr_fillColor = getIfExists_const(foptProperties, FIELDID_FILL_COLOR); const unsigned *ptr_fillBackColor = getIfExists_const(foptProperties, FIELDID_FILL_BACK_COLOR); unsigned fill = ptr_fillColor ? *ptr_fillColor : 0x00FFFFFFF; unsigned fillBack = ptr_fillBackColor ? *ptr_fillBackColor : 0x00FFFFFF; ColorReference firstColor(fill, fill); ColorReference secondColor(fill, fillBack); const unsigned *ptr_fillOpacity = getIfExists_const(foptProperties, FIELDID_FILL_OPACITY); const unsigned *ptr_fillBackOpacity = getIfExists_const(foptProperties, FIELDID_FILL_BACK_OPACITY); const unsigned *ptr_fillFocus = getIfExists_const(foptProperties, FIELDID_FILL_FOCUS); short fillFocus = ptr_fillFocus ? int((*ptr_fillFocus << 16) >> 16) : 0; angle = ptr_angle ? *ptr_angle : 0; angle >>= 16; //it's actually only 16 bits // Don't try to figure out what sense the following switch statement makes. // The angles are just offset by 90 degrees in the file format in some cases. // It seems totally arbitrary -- maybe an MS bug ? switch (angle) { case -135: angle = -45; break; case -45: angle = 225; break; default: break; } double fillLeftVal = 0.0; const unsigned *ptr_fillLeft = getIfExists_const(foptProperties, FIELDID_FILL_TO_LEFT); if (ptr_fillLeft) fillLeftVal = toFixedPoint(*ptr_fillLeft); double fillTopVal = 0.0; const unsigned *ptr_fillTop = getIfExists_const(foptProperties, FIELDID_FILL_TO_TOP); if (ptr_fillTop) fillTopVal = toFixedPoint(*ptr_fillTop); double fillRightVal = 0.0; const unsigned *ptr_fillRight = getIfExists_const(foptProperties, FIELDID_FILL_TO_RIGHT); if (ptr_fillRight) fillRightVal = toFixedPoint(*ptr_fillRight); double fillBottomVal = 0.0; const unsigned *ptr_fillBottom = getIfExists_const(foptProperties, FIELDID_FILL_TO_BOTTOM); if (ptr_fillBottom) fillBottomVal = toFixedPoint(*ptr_fillBottom); std::shared_ptr ret(new GradientFill(m_collector, angle, (int)fillType)); ret->setFillCenter(fillLeftVal, fillTopVal, fillRightVal, fillBottomVal); const unsigned *ptr_fillGrad = getIfExists_const(foptProperties, FIELDID_FILL_SHADE_COMPLEX); if (ptr_fillGrad) { const std::vector gradientData = foptValues[FIELDID_FILL_SHADE_COMPLEX]; if (gradientData.size() > 6) { unsigned short numEntries = gradientData[0] | (gradientData[1] << 8); unsigned offs = 6; for (unsigned i = 0; i < numEntries; ++i) { unsigned color = gradientData[offs] | (unsigned(gradientData[offs + 1]) << 8) | (unsigned(gradientData[offs + 2]) << 16) | (unsigned(gradientData[offs + 3]) << 24); offs += 4; auto posi = (int)(toFixedPoint(gradientData[offs] | (unsigned(gradientData[offs + 1]) << 8) | (unsigned(gradientData[offs + 2]) << 16) | (unsigned(gradientData[offs + 3]) << 24)) * 100); offs += 4; ColorReference sColor(color, color); if (fillFocus == 0) ret->addColor(sColor, posi, ptr_fillOpacity ? (double)(*ptr_fillOpacity) / 0xFFFF : 1); else if (fillFocus == 100) ret->addColorReverse(sColor, 100 - posi, ptr_fillOpacity ? (double)(*ptr_fillOpacity) / 0xFFFF : 1); else if (fillFocus > 0) ret->addColor(sColor, posi / 2, ptr_fillOpacity ? (double)(*ptr_fillOpacity) / 0xFFFF : 1); else if (fillFocus < 0) ret->addColorReverse(sColor, (100 - posi) / 2, ptr_fillOpacity ? (double)(*ptr_fillOpacity) / 0xFFFF : 1); } if ((fillFocus < 0) || ((fillFocus > 0) && (fillFocus < 100))) ret->completeComplexFill(); } } else { if (fillFocus == 0) { ret->addColor(firstColor, 0, ptr_fillOpacity ? (double)(*ptr_fillOpacity) / 0xFFFF : 1); ret->addColor(secondColor, 100, ptr_fillBackOpacity ? (double)(*ptr_fillBackOpacity) / 0xFFFF : 1); } else if (fillFocus == 100) { ret->addColor(secondColor, 0, ptr_fillBackOpacity ? (double)(*ptr_fillBackOpacity) / 0xFFFF : 1); ret->addColor(firstColor, 100, ptr_fillOpacity ? (double)(*ptr_fillOpacity) / 0xFFFF : 1); } else if (fillFocus > 0) { ret->addColor(secondColor, 0, ptr_fillBackOpacity ? (double)(*ptr_fillBackOpacity) / 0xFFFF : 1); ret->addColor(firstColor, fillFocus, ptr_fillOpacity ? (double)(*ptr_fillOpacity) / 0xFFFF : 1); ret->addColor(secondColor, 100, ptr_fillBackOpacity ? (double)(*ptr_fillBackOpacity) / 0xFFFF : 1); // ret->addColor(firstColor, 0, ptr_fillOpacity ? (double)(*ptr_fillOpacity) / 0xFFFF : 1); // ret->addColor(secondColor, fillFocus, ptr_fillBackOpacity ? (double)(*ptr_fillBackOpacity) / 0xFFFF : 1); // ret->addColor(firstColor, 100, ptr_fillOpacity ? (double)(*ptr_fillOpacity) / 0xFFFF : 1); } else if (fillFocus < 0) { ret->addColor(firstColor, 0, ptr_fillOpacity ? (double)(*ptr_fillOpacity) / 0xFFFF : 1); ret->addColor(secondColor, 100 + fillFocus, ptr_fillBackOpacity ? (double)(*ptr_fillBackOpacity) / 0xFFFF : 1); ret->addColor(firstColor, 100, ptr_fillOpacity ? (double)(*ptr_fillOpacity) / 0xFFFF : 1); // ret->addColor(secondColor, 0, ptr_fillBackOpacity ? (double)(*ptr_fillBackOpacity) / 0xFFFF : 1); // ret->addColor(firstColor, 100 + fillFocus, ptr_fillOpacity ? (double)(*ptr_fillOpacity) / 0xFFFF : 1); // ret->addColor(secondColor, 100, ptr_fillBackOpacity ? (double)(*ptr_fillBackOpacity) / 0xFFFF : 1); } } return ret; } case TEXTURE: case BITMAP: { // in the case the shape is rotated we must rotate the image too int rotation = 0; const int *ptr_rotation = (const int *)getIfExists_const(foptProperties, FIELDID_ROTATION); if (ptr_rotation) rotation = (int)doubleModulo(toFixedPoint(*ptr_rotation), 360); const unsigned *ptr_bgPxId = getIfExists_const(foptProperties, FIELDID_BG_PXID); if (ptr_bgPxId && *ptr_bgPxId > 0 && *ptr_bgPxId <= m_escherDelayIndices.size() && m_escherDelayIndices[*ptr_bgPxId - 1] >= 0) { return std::shared_ptr(new ImgFill(m_escherDelayIndices[*ptr_bgPxId - 1], m_collector, fillType == TEXTURE, rotation)); } return std::shared_ptr(); } case PATTERN: { const unsigned *ptr_bgPxId = getIfExists_const(foptProperties, FIELDID_BG_PXID); const unsigned *ptr_fillColor = getIfExists_const(foptProperties, FIELDID_FILL_COLOR); const unsigned *ptr_fillBackColor = getIfExists_const(foptProperties, FIELDID_FILL_BACK_COLOR); ColorReference fill = ptr_fillColor ? ColorReference(*ptr_fillColor) : ColorReference(0x00FFFFFF); // ColorReference back = ptr_fillBackColor ? ColorReference(*ptr_fillBackColor) : ColorReference(0x08000000); ColorReference back = ptr_fillBackColor ? ColorReference(*ptr_fillBackColor) : ColorReference(0x00FFFFFF); if (ptr_bgPxId && *ptr_bgPxId > 0 && *ptr_bgPxId <= m_escherDelayIndices.size() && m_escherDelayIndices[*ptr_bgPxId - 1] >= 0) { return std::shared_ptr(new PatternFill(m_escherDelayIndices[*ptr_bgPxId - 1], m_collector, fill, back)); } return std::shared_ptr(); } case BACKGROUND: default: return std::shared_ptr(); } } DynamicCustomShape MSPUBParser::getDynamicCustomShape( const std::vector &vertexData, const std::vector &segmentData, const std::vector &guideData, unsigned geoWidth, unsigned geoHeight) { DynamicCustomShape ret(geoWidth, geoHeight); ret.m_vertices = parseVertices(vertexData); ret.m_elements = parseSegments(segmentData); ret.m_calculations = parseGuides(guideData); return ret; } std::vector MSPUBParser::parseSegments( const std::vector &segmentData) { std::vector ret; if (segmentData.size() < 6) { return ret; } // assume that the entry size is 2. unsigned short numEntries = segmentData[0] | (segmentData[1] << 8); unsigned offset = 6; for (unsigned i = 0; i < numEntries; ++i) { if (offset + 2 > segmentData.size()) { break; } ret.push_back(segmentData[offset] | (segmentData[offset + 1] << 8)); offset += 2; } return ret; } std::vector MSPUBParser::parseGuides( const std::vector &/* guideData */) { std::vector ret; //FIXME : implement this function. return ret; } std::vector MSPUBParser::parseVertices( const std::vector &vertexData) { std::vector ret; if (vertexData.size() < 6) { return ret; } unsigned short numVertices = vertexData[0] | (vertexData[1] << 8); unsigned short entrySize = vertexData[4] | (vertexData[5] << 8); if (entrySize == 0xFFF0) { entrySize = 4; } if (!(entrySize == 2 || entrySize == 4 || entrySize == 8)) { MSPUB_DEBUG_MSG(("Incomprehensible entry size %u in vertex complex data!\n", entrySize)); return ret; } unsigned offset = 6; ret.reserve(numVertices); for (unsigned i = 0; i < numVertices; ++i) { if (offset + entrySize > vertexData.size()) { break; } int x, y; switch (entrySize) { case 2: x = vertexData[offset]; y = vertexData[offset + 1]; break; case 4: x = vertexData[offset] | (unsigned(vertexData[offset + 1]) << 8); y = vertexData[offset + 2] | (unsigned(vertexData[offset + 3]) << 8); break; case 8: x = vertexData[offset] | (unsigned(vertexData[offset + 1]) << 8) | (unsigned(vertexData[offset + 2]) << 16) | (unsigned(vertexData[offset + 3]) << 24); y = vertexData[offset + 4] | (unsigned(vertexData[offset + 5]) << 8) | (unsigned(vertexData[offset + 6]) << 16) | (unsigned(vertexData[offset + 7]) << 24); break; default: // logically shouldn't be able to get here. x = 0; y = 0; break; } Vertex v = {x, y}; ret.push_back(v); offset += entrySize; } return ret; } unsigned MSPUBParser::getEscherElementTailLength(unsigned short type) { switch (type) { case OFFICE_ART_DGG_CONTAINER: case OFFICE_ART_DG_CONTAINER: return 4; default: return 0; } } unsigned MSPUBParser::getEscherElementAdditionalHeaderLength(unsigned short type) { switch (type) { case OFFICE_ART_CLIENT_ANCHOR: case OFFICE_ART_CLIENT_DATA: //account for the fact that the length appears twice, for whatever reason return 4; } return 0; } bool MSPUBParser::findEscherContainerWithTypeInSet(librevenge::RVNGInputStream *input, const EscherContainerInfo &parent, EscherContainerInfo &out, std::set types) { while (stillReading(input, parent.contentsOffset + parent.contentsLength)) { EscherContainerInfo next = parseEscherContainer(input); if (types.find(next.type) != types.end()) { out = next; return true; } input->seek(next.contentsOffset + next.contentsLength + getEscherElementTailLength(next.type), librevenge::RVNG_SEEK_SET); } return false; } bool MSPUBParser::findEscherContainer(librevenge::RVNGInputStream *input, const EscherContainerInfo &parent, EscherContainerInfo &out, unsigned short desiredType) { MSPUB_DEBUG_MSG(("At offset 0x%lx, attempting to find escher container of type 0x%x\n", input->tell(), desiredType)); while (stillReading(input, parent.contentsOffset + parent.contentsLength)) { EscherContainerInfo next = parseEscherContainer(input); if (next.type == desiredType) { out = next; return true; } input->seek(next.contentsOffset + next.contentsLength + getEscherElementTailLength(next.type), librevenge::RVNG_SEEK_SET); } return false; } FOPTValues MSPUBParser::extractFOPTValues(librevenge::RVNGInputStream *input, const EscherContainerInfo &record) { FOPTValues ret; input->seek(record.contentsOffset, librevenge::RVNG_SEEK_SET); unsigned short numValues = record.initial >> 4; std::vector complexIds; for (unsigned short i = 0; i < numValues; ++i) { if (!stillReading(input, record.contentsOffset + record.contentsLength)) { break; } unsigned short id = readU16(input); unsigned value = readU32(input); ret.m_scalarValues[id] = value; bool complex = id & 0x8000; if (complex) { complexIds.push_back(id); } } for (unsigned short id : complexIds) { if (!stillReading(input, record.contentsOffset + record.contentsLength)) { break; } unsigned length = ret.m_scalarValues[id]; if (!length) { continue; } unsigned short numEntries = readU16(input); input->seek(2, librevenge::RVNG_SEEK_CUR); unsigned short entryLength = readU16(input); if (entryLength == 0xFFF0) { entryLength = 4; } input->seek(-6, librevenge::RVNG_SEEK_CUR); readNBytes(input, static_cast(entryLength) * numEntries + 6, ret.m_complexValues[id]); } return ret; } std::map MSPUBParser::extractEscherValues(librevenge::RVNGInputStream *input, const EscherContainerInfo &record) { std::map ret; input->seek(record.contentsOffset + getEscherElementAdditionalHeaderLength(record.type), librevenge::RVNG_SEEK_SET); while (stillReading(input, record.contentsOffset + record.contentsLength)) { unsigned short id = readU16(input); if (id == 0) { if (!stillReading(input, record.contentsOffset + record.contentsLength)) break; MSPUB_DEBUG_MSG(("found escher value with ID 0!\n")); } unsigned value = readU32(input); ret[id] = value; } return ret; } bool MSPUBParser::parseContentChunkReference(librevenge::RVNGInputStream *input, const MSPUBBlockInfo block) { //input should be at block.dataOffset + 4 , that is, at the beginning of the list of sub-blocks MSPUB_DEBUG_MSG(("Parsing chunk reference 0x%x\n", m_lastSeenSeqNum)); unsigned type = UNKNOWN_CHUNK; unsigned long offset = 0; unsigned parentSeqNum = 0; bool seenType = false; bool seenOffset = false; bool seenParentSeqNum = false; while (stillReading(input, block.dataOffset + block.dataLength)) { MSPUBBlockInfo subBlock = parseBlock(input, true); //FIXME: Warn if multiple of these blocks seen. if (subBlock.id == CHUNK_TYPE) { type = subBlock.data; seenType = true; } else if (subBlock.id == CHUNK_OFFSET) { offset = subBlock.data; seenOffset = true; } else if (subBlock.id == CHUNK_PARENT_SEQNUM) { parentSeqNum = subBlock.data; seenParentSeqNum = true; } } if (seenType && seenOffset) //FIXME: What if there is an offset, but not a type? Should we still set the end of the preceding chunk to that offset? { if (type == PAGE) { MSPUB_DEBUG_MSG(("page chunk: offset 0x%lx, seqnum 0x%x\n", offset, m_lastSeenSeqNum)); m_contentChunks.push_back(ContentChunkReference(type, offset, 0, m_lastSeenSeqNum, seenParentSeqNum ? parentSeqNum : 0)); m_pageChunkIndices.push_back(unsigned(m_contentChunks.size() - 1)); return true; } else if (type == DOCUMENT) { MSPUB_DEBUG_MSG(("document chunk: offset 0x%lx, seqnum 0x%x\n", offset, m_lastSeenSeqNum)); m_contentChunks.push_back(ContentChunkReference(type, offset, 0, m_lastSeenSeqNum, seenParentSeqNum ? parentSeqNum : 0)); m_documentChunkIndex = unsigned(m_contentChunks.size() - 1); return true; } else if (type == SHAPE || type == ALTSHAPE || type == GROUP || type == TABLE || type == LOGO) { MSPUB_DEBUG_MSG(("shape chunk: offset 0x%lx, seqnum 0x%x, parent seqnum: 0x%x\n", offset, m_lastSeenSeqNum, parentSeqNum)); m_contentChunks.push_back(ContentChunkReference(type, offset, 0, m_lastSeenSeqNum, seenParentSeqNum ? parentSeqNum : 0)); m_shapeChunkIndices.push_back(unsigned(m_contentChunks.size() - 1)); if (type == ALTSHAPE) { m_alternateShapeSeqNums.push_back(m_lastSeenSeqNum); } return true; } else if (type == CELLS) { m_contentChunks.push_back(ContentChunkReference(type, offset, 0, m_lastSeenSeqNum, seenParentSeqNum ? parentSeqNum : 0)); m_cellsChunkIndices.push_back(unsigned(m_contentChunks.size() - 1)); return true; } else if (type == PALETTE) { m_contentChunks.push_back(ContentChunkReference(type, offset, 0, m_lastSeenSeqNum, seenParentSeqNum ? parentSeqNum : 0)); m_paletteChunkIndices.push_back(unsigned(m_contentChunks.size() - 1)); return true; } else if (type == BORDER_ART) { m_contentChunks.push_back(ContentChunkReference(type, offset, 0, m_lastSeenSeqNum, seenParentSeqNum ? parentSeqNum : 0)); m_borderArtChunkIndices.push_back( unsigned(m_contentChunks.size() - 1)); return true; } else if (type == FONT) { m_contentChunks.push_back(ContentChunkReference(type, offset, 0, m_lastSeenSeqNum, seenParentSeqNum ? parentSeqNum : 0)); m_fontChunkIndices.push_back(unsigned(m_contentChunks.size() - 1)); return true; } m_contentChunks.push_back(ContentChunkReference(type, offset, 0, m_lastSeenSeqNum, seenParentSeqNum ? parentSeqNum : 0)); m_unknownChunkIndices.push_back(unsigned(m_contentChunks.size() - 1)); } return false; } bool MSPUBParser::isBlockDataString(unsigned type) { return type == STRING_CONTAINER; } void MSPUBParser::skipBlock(librevenge::RVNGInputStream *input, MSPUBBlockInfo block) { input->seek(block.dataOffset + block.dataLength, librevenge::RVNG_SEEK_SET); } EscherContainerInfo MSPUBParser::parseEscherContainer(librevenge::RVNGInputStream *input) { EscherContainerInfo info; info.initial = readU16(input); info.type = readU16(input); info.contentsLength = readU32(input); info.contentsOffset = input->tell(); MSPUB_DEBUG_MSG(("Parsed escher container: type 0x%x, contentsOffset 0x%lx, contentsLength 0x%lx\n", info.type, info.contentsOffset, info.contentsLength)); return info; } MSPUBBlockInfo MSPUBParser::parseBlock(librevenge::RVNGInputStream *input, bool skipHierarchicalData) { MSPUBBlockInfo info; info.startPosition = input->tell(); info.id = readU8(input); info.type = readU8(input); info.dataOffset = input->tell(); int len = getBlockDataLength(info.type); bool varLen = len < 0; if (varLen) { info.dataLength = readU32(input); if (isBlockDataString(info.type)) { info.stringData = std::vector(); readNBytes(input, info.dataLength - 4, info.stringData); } else if (skipHierarchicalData) { skipBlock(input, info); } info.data = 0; } else { info.dataLength = len; switch (info.dataLength) { case 1: info.data = readU8(input); break; case 2: info.data = readU16(input); break; case 4: info.data = readU32(input); break; case 8: case 16: case 24: //FIXME: Not doing anything with this data for now. skipBlock(input, info); MSPUB_FALLTHROUGH; default: info.data = 0; } } MSPUB_DEBUG_MSG(("parseBlock dataOffset 0x%lx, id 0x%x, type 0x%x, dataLength 0x%lx, integral data 0x%x\n", info.dataOffset, info.id, info.type, info.dataLength, info.data)); return info; } PageType MSPUBParser::getPageTypeBySeqNum(unsigned seqNum) { switch (seqNum) { case 0x10d: case 0x110: case 0x113: case 0x117: return DUMMY_PAGE; default: return NORMAL; } } bool MSPUBParser::parsePaletteChunk(librevenge::RVNGInputStream *input, const ContentChunkReference &chunk) { unsigned length = readU32(input); while (stillReading(input, chunk.offset + length)) { MSPUBBlockInfo info = parseBlock(input); if (info.type == 0xA0) { while (stillReading(input, info.dataOffset + info.dataLength)) { MSPUBBlockInfo subInfo = parseBlock(input); if (subInfo.type == GENERAL_CONTAINER) { parsePaletteEntry(input, subInfo); } else if (subInfo.type == DUMMY) { m_collector->addPaletteColor(Color()); } skipBlock(input, subInfo); } } skipBlock(input, info); } return true; } void MSPUBParser::parsePaletteEntry(librevenge::RVNGInputStream *input, MSPUBBlockInfo info) { while (stillReading(input, info.dataOffset + info.dataLength)) { MSPUBBlockInfo subInfo = parseBlock(input, true); if (subInfo.id == 0x01) { m_collector->addPaletteColor(Color(subInfo.data & 0xFF, (subInfo.data >> 8) & 0xFF, (subInfo.data >> 16) & 0xFF)); } } } bool MSPUBParser::parseMetaData() { m_input->seek(0, librevenge::RVNG_SEEK_SET); MSPUBMetaData metaData; std::unique_ptr sumaryInfo(m_input->getSubStreamByName("\x05SummaryInformation")); if (sumaryInfo) { metaData.parse(sumaryInfo.get()); } std::unique_ptr docSumaryInfo(m_input->getSubStreamByName("\005DocumentSummaryInformation")); if (docSumaryInfo) { metaData.parse(docSumaryInfo.get()); } m_input->seek(0, librevenge::RVNG_SEEK_SET); metaData.parseTimes(m_input); m_collector->collectMetaData(metaData.getMetaData()); return true; } } /* vim:set shiftwidth=2 softtabstop=2 expandtab: */