/* -*- Mode: C++; c-default-style: "k&r"; indent-tabs-mode: nil; tab-width: 2; c-basic-offset: 2 -*- */
/* libmwaw
* Version: MPL 2.0 / LGPLv2+
*
* The contents of this file are subject to the Mozilla Public License Version
* 2.0 (the "License"); you may not use this file except in compliance with
* the License or as specified alternatively below. You may obtain a copy of
* the License at http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* Major Contributor(s):
* Copyright (C) 2002 William Lachance (wrlach@gmail.com)
* Copyright (C) 2002,2004 Marc Maurer (uwog@uwog.net)
* Copyright (C) 2004-2006 Fridrich Strba (fridrich.strba@bluewin.ch)
* Copyright (C) 2006, 2007 Andrew Ziem
* Copyright (C) 2011, 2012 Alonso Laurent (alonso@loria.fr)
*
*
* All Rights Reserved.
*
* For minor contributions see the git repository.
*
* Alternatively, the contents of this file may be used under the terms of
* the GNU Lesser General Public License Version 2 or later (the "LGPLv2+"),
* in which case the provisions of the LGPLv2+ are applicable
* instead of those above.
*/
#include <iomanip>
#include <iostream>
#include <limits>
#include <set>
#include <sstream>
#include <librevenge/librevenge.h>
#include "MWAWTextListener.hxx"
#include "MWAWFontConverter.hxx"
#include "MWAWHeader.hxx"
#include "MWAWPictData.hxx"
#include "MWAWPosition.hxx"
#include "MWAWPrinter.hxx"
#include "MWAWRSRCParser.hxx"
#include "MWAWSubDocument.hxx"
#include "DocMkrText.hxx"
#include "DocMkrParser.hxx"
/** Internal: the structures of a DocMkrParser */
namespace DocMkrParserInternal
{
////////////////////////////////////////
//! Internal: store a picture information in DocMkrParser
struct PictInfo {
//! constructor
PictInfo()
: m_id(-1)
, m_sndId(-1)
, m_align(1)
, m_print(false)
, m_invert(false)
, m_action(0)
, m_actionString("")
, m_extra("")
{
for (auto &next : m_next) next=0;
}
//! operator<<
friend std::ostream &operator<<(std::ostream &o, PictInfo const &info);
//! the picture id
int m_id;
//! the sound id
int m_sndId;
//! the alignment ( 1:center, ... )
int m_align;
//! true if the picture is printed
bool m_print;
//! true if we must invert the picture
bool m_invert;
//! the action
int m_action;
//! the action string
std::string m_actionString;
//! the next chapter/paragraph position for goChapter
int m_next[2];
//! the applescript type
std::string m_appleScript[3];
//! extra data
std::string m_extra;
};
std::ostream &operator<<(std::ostream &o, PictInfo const &info)
{
if (info.m_id >= 0) o << "pictId=" << info.m_id << ",";
switch (info.m_align) {
case 1:
o << "center,";
break;
case 2:
o << "left,";
break;
case 3:
o << "right,";
break;
default:
o << "#align=" << info.m_align << ",";
break;
}
if (info.m_action >= 0 && info.m_action <= 16) {
static char const *wh[]= {
"", "goTo", "aboutDialog", "print", "quit", "launch", "sound", "QMOV",
"note", "export[asText]", "last[chapter]", "TOC[show]", "find", "appleEvent",
"next[chapter]", "prev[chapter]", "script"
};
o << wh[info.m_action];
}
else
o << "#action=" << info.m_action << ",";
switch (info.m_action) {
case 1:
o << "[chapter=" << info.m_next[0];
if (info.m_next[1]) o << ",para=" << info.m_next[1] << "]";
else o << "]";
break;
case 5:
case 7:
case 8:
case 0x10:
o << "[" << info.m_actionString << "]";
break;
case 6:
o << "[id=" << info.m_sndId << "]";
break;
case 0xd:
o << "[appli=" << info.m_appleScript[0] << ",class=" << info.m_appleScript[1]
<< ",eventid=" << info.m_appleScript[2];
if (info.m_actionString.size())
o << ",data=" <<info.m_actionString;
o << "]";
break;
default:
break;
}
o << "],";
if (!info.m_print) o << "noPrint,";
if (info.m_invert) o << "invert,";
o << info.m_extra;
return o;
}
////////////////////////////////////////
//! Internal: the state of a DocMkrParser
struct State {
//! constructor
State()
: m_idPictEntryMap()
, m_idPictInfoMap()
, m_zonePictInfoUnit(100)
, m_actPage(0)
, m_numPages(0)
, m_headerHeight(0)
, m_footerHeight(0)
{
}
//! return a pictinfo id corresponding to a zone and a local if
int pictInfoId(int zId, int lId) const
{
return (zId+2)*m_zonePictInfoUnit+lId;
}
//! try to find the picture info unit (fixme: a hack)
void findPictInfoUnit(int nZones);
//! a map id->pictEntry
std::map<int,MWAWEntry> m_idPictEntryMap;
//! a map id->pictInfo
std::map<int,PictInfo> m_idPictInfoMap;
//! the zone unit to retrieve pictInfo
int m_zonePictInfoUnit;
int m_actPage /** the actual page */, m_numPages /** the number of page of the final document */;
int m_headerHeight /** the header height if known */,
m_footerHeight /** the footer height if known */;
};
void State::findPictInfoUnit(int nZones)
{
if (m_idPictInfoMap.empty())
return;
bool is100=true, is1000=true;
for (auto it : m_idPictInfoMap) {
int id=it.first;
if (id > (nZones+3)*100 || id < 200)
is100=false;
if (id > (nZones+3)*1000 || id < 2000)
is1000=false;
}
if (is100 && !is1000)
m_zonePictInfoUnit=100;
else if (is1000 && !is100)
m_zonePictInfoUnit=1000;
else {
MWAW_DEBUG_MSG(("DocMkrParserInternal::State::findPictInfoUnit can not find unit\n"));
}
}
}
////////////////////////////////////////////////////////////
// constructor/destructor, ...
////////////////////////////////////////////////////////////
DocMkrParser::DocMkrParser(MWAWInputStreamPtr const &input, MWAWRSRCParserPtr const &rsrcParser, MWAWHeader *header)
: MWAWTextParser(input, rsrcParser, header)
, m_state()
, m_textParser()
{
init();
}
DocMkrParser::~DocMkrParser()
{
}
void DocMkrParser::init()
{
resetTextListener();
m_state.reset(new DocMkrParserInternal::State);
m_textParser.reset(new DocMkrText(*this));
}
MWAWInputStreamPtr DocMkrParser::rsrcInput()
{
return getRSRCParser()->getInput();
}
libmwaw::DebugFile &DocMkrParser::rsrcAscii()
{
return getRSRCParser()->ascii();
}
////////////////////////////////////////////////////////////
// new page
////////////////////////////////////////////////////////////
void DocMkrParser::newPage(int number)
{
if (number <= m_state->m_actPage || number > m_state->m_numPages)
return;
while (m_state->m_actPage < number) {
m_state->m_actPage++;
if (!getTextListener() || m_state->m_actPage == 1)
continue;
getTextListener()->insertBreak(MWAWTextListener::PageBreak);
}
}
////////////////////////////////////////////////////////////
// the parser
////////////////////////////////////////////////////////////
void DocMkrParser::parse(librevenge::RVNGTextInterface *docInterface)
{
if (!getInput().get() || !getRSRCParser() || !checkHeader(nullptr)) throw(libmwaw::ParseException());
bool ok = false;
try {
checkHeader(nullptr);
ok = createZones();
if (ok) {
createDocument(docInterface);
m_textParser->sendMainText();
m_textParser->sendTOC();
#ifdef DEBUG
m_textParser->flushExtra();
flushExtra();
#endif
}
ascii().reset();
}
catch (...) {
MWAW_DEBUG_MSG(("DocMkrParser::parse: exception catched when parsing\n"));
ok = false;
}
resetTextListener();
if (!ok) throw(libmwaw::ParseException());
}
////////////////////////////////////////////////////////////
// create the document
////////////////////////////////////////////////////////////
void DocMkrParser::createDocument(librevenge::RVNGTextInterface *documentInterface)
{
if (!documentInterface) return;
if (getTextListener()) {
MWAW_DEBUG_MSG(("DocMkrParser::createDocument: listener already exist\n"));
return;
}
// update the page
m_state->m_actPage = 0;
// create the page list
std::vector<MWAWPageSpan> pageList;
m_textParser->updatePageSpanList(pageList);
m_state->m_numPages = int(pageList.size());
//
MWAWTextListenerPtr listen(new MWAWTextListener(*getParserState(), pageList, documentInterface));
setTextListener(listen);
listen->startDocument();
}
////////////////////////////////////////////////////////////
//
// Intermediate level
//
////////////////////////////////////////////////////////////
bool DocMkrParser::createZones()
{
MWAWInputStreamPtr input = getInput();
MWAWRSRCParserPtr rsrcParser = getRSRCParser();
auto &entryMap = rsrcParser->getEntriesMap();
if (!m_textParser->createZones())
return false;
// the different pict zones
auto it = entryMap.lower_bound("PICT");
while (it != entryMap.end()) {
if (it->first != "PICT")
break;
MWAWEntry const &entry = it++->second;
m_state->m_idPictEntryMap[entry.id()]=entry;
}
it = entryMap.lower_bound("conp"); // local picture 5000-...?
while (it != entryMap.end()) {
if (it->first != "conp")
break;
MWAWEntry const &entry = it++->second;
librevenge::RVNGBinaryData data;
rsrcParser->parsePICT(entry, data);
}
it = entryMap.lower_bound("pInf"); // 201|202,301..309,401..410,501..
while (it != entryMap.end()) {
if (it->first != "pInf")
break;
MWAWEntry const &entry = it++->second;
readPictInfo(entry);
}
// chapiter name STR 2000..
// entry 0: copyright
it = entryMap.lower_bound("Dk@P");
while (it != entryMap.end()) {
if (it->first != "Dk@P")
break;
MWAWEntry const &entry = it++->second;
std::string str;
rsrcParser->parseSTR(entry, str);
}
it = entryMap.lower_bound("sTwD");
while (it != entryMap.end()) {
if (it->first != "sTwD")
break;
MWAWEntry const &entry = it++->second;
readSTwD(entry);
}
it = entryMap.lower_bound("xtr2");
while (it != entryMap.end()) {
if (it->first != "xtr2")
break;
MWAWEntry const &entry = it++->second;
readXtr2(entry);
}
// 1000:docname, 1001:footer name, 2001-... chapter name, others ...
it = entryMap.lower_bound("STR ");
while (it != entryMap.end()) {
if (it->first != "STR ")
break;
MWAWEntry const &entry = it++->second;
std::string str;
rsrcParser->parseSTR(entry, str);
}
m_state->findPictInfoUnit(m_textParser->numChapters());
#ifdef DEBUG_WITH_FILES
// get rid of the default application resource
libmwaw::DebugFile &ascFile = rsrcAscii();
static char const *appliRsrc[]= {
// default, Dialog (3000: DLOG,DITL,DLGX,dctb","ictb","STR ")
"ALRT","BNDL","CNTL","CURS","CDEF", "DLOG","DLGX","DITL","FREF","ICON", "ICN#","MENU","SIZE",
"crsr","dctb","icl4","icl8","ics4", "ics8","ics#","ictb","snd ",
// local
"mstr" /* menu string */, "aete" /* some function name?*/
};
for (int r=0; r < 13+9+2; r++) {
it = entryMap.lower_bound(appliRsrc[r]);
while (it != entryMap.end()) {
if (it->first != appliRsrc[r])
break;
MWAWEntry const &entry = it++->second;
if (entry.isParsed()) continue;
entry.setParsed(true);
ascFile.skipZone(entry.begin()-4,entry.end()-1);
}
}
#endif
return true;
}
void DocMkrParser::flushExtra()
{
MWAWRSRCParserPtr rsrcParser = getRSRCParser();
for (auto it : m_state->m_idPictEntryMap) {
MWAWEntry const &entry=it.second;
if (entry.isParsed()) continue;
librevenge::RVNGBinaryData data;
rsrcParser->parsePICT(entry,data);
}
}
////////////////////////////////////////////////////////////
//
// Low level
//
////////////////////////////////////////////////////////////
bool DocMkrParser::sendPicture(int zId, int lId, double /*lineW*/)
{
int pictId=m_state->pictInfoId(zId,lId);
auto it=m_state->m_idPictInfoMap.find(pictId);
if (it==m_state->m_idPictInfoMap.end()) {
MWAW_DEBUG_MSG(("DocMkrText::sendPicture: can not find picture for zone=%d, id=%d\n",zId,lId));
return false;
}
DocMkrParserInternal::PictInfo const &info=it->second;
if (m_state->m_idPictEntryMap.find(info.m_id)==m_state->m_idPictEntryMap.end()) {
MWAW_DEBUG_MSG(("DocMkrText::sendPicture: can not find picture for id=%d\n",info.m_id));
return false;
}
if (!getTextListener()) {
MWAW_DEBUG_MSG(("DocMkrText::sendPicture: can not find the listener\n"));
return false;
}
if (info.m_action==8 && info.m_actionString.size())
m_textParser->sendComment(info.m_actionString);
MWAWInputStreamPtr input = rsrcInput();
MWAWRSRCParserPtr rsrcParser = getRSRCParser();
MWAWEntry const &entry=m_state->m_idPictEntryMap.find(info.m_id)->second;
librevenge::RVNGBinaryData data;
long pos = input->tell();
rsrcParser->parsePICT(entry,data);
input->seek(pos,librevenge::RVNG_SEEK_SET);
auto dataSz=int(data.size());
if (!dataSz) {
return false;
}
MWAWInputStreamPtr pictInput=MWAWInputStream::get(data, false);
if (!pictInput) {
MWAW_DEBUG_MSG(("DocMkrText::sendPicture: oops can not find an input\n"));
return false;
}
MWAWBox2f box;
auto res = MWAWPictData::check(pictInput, dataSz,box);
if (res == MWAWPict::MWAW_R_BAD) {
MWAW_DEBUG_MSG(("DocMkrText::sendPicture: can not find the picture\n"));
return false;
}
pictInput->seek(0,librevenge::RVNG_SEEK_SET);
std::shared_ptr<MWAWPict> thePict(MWAWPictData::get(pictInput, dataSz));
MWAWPosition pictPos=MWAWPosition(MWAWVec2f(0,0),box.size(), librevenge::RVNG_POINT);
auto xpos= (info.m_align==1) ? MWAWPosition::XCenter :
(info.m_align==3) ? MWAWPosition::XRight : MWAWPosition::XLeft;
pictPos.setRelativePosition(MWAWPosition::Paragraph, xpos);
pictPos.m_wrapping = MWAWPosition::WRunThrough;
if (thePict) {
MWAWEmbeddedObject picture;
if (thePict->getBinary(picture))
getTextListener()->insertPicture(pictPos, picture);
}
return true;
}
bool DocMkrParser::readPictInfo(MWAWEntry const &entry)
{
long length = entry.length();
if (!entry.valid() || length<8) {
MWAW_DEBUG_MSG(("DocMkrText::readPictInfo: the entry seems very short\n"));
return false;
}
entry.setParsed(true);
long pos = entry.begin();
long endPos = entry.end();
MWAWInputStreamPtr input = rsrcInput();
libmwaw::DebugFile &ascFile = rsrcAscii();
input->seek(pos, librevenge::RVNG_SEEK_SET);
libmwaw::DebugStream f;
DocMkrParserInternal::PictInfo info;
info.m_id = static_cast<int>(input->readULong(2));
info.m_align = static_cast<int>(input->readLong(2));
auto val =static_cast<int>(input->readLong(2)); // 0|1
if (val) f << "unkn=" << val << ",";
auto action =static_cast<int>(input->readLong(2)); // 0..b
auto extraN = int(endPos-input->tell());
if (action < 0) {
info.m_invert=true;
action = -action;
}
info.m_action=action;
switch (action) {
case 1:
if (extraN < 2) {
f << "actionArg##,";
break;
}
info.m_next[0]=static_cast<int>(input->readLong(2));
if (extraN < 4)
break;
info.m_next[1]=static_cast<int>(input->readLong(2));
break;
case 5:
case 7:
case 8:
case 0x10: {
if (extraN < 1) {
f << "actionArg##,";
}
auto fSz=static_cast<int>(input->readULong(1));
if (extraN < fSz+1) {
f << "##[N=" << fSz << "],";
break;
}
std::string name("");
for (int i = 0; i < fSz; i++)
name += char(input->readULong(1));
info.m_actionString=name;
break;
}
case 6: {
if (extraN < 4) {
f << "actionArg##,";
break;
}
info.m_sndId=static_cast<int>(input->readULong(2));
val = static_cast<int>(input->readULong(2)); // loop?
if (val)
f << "sndFlag=" << val << ",";
break;
}
case 0xd: {
if (extraN < 13) {
f << "actionArg##,";
break;
}
for (auto &w : info.m_appleScript) {
std::string name("");
for (int i = 0; i < 4; i++)
name += char(input->readULong(1));
w=name;
}
auto fSz=static_cast<int>(input->readULong(1));
if (extraN < fSz+13) {
f << "##[N=" << fSz << "],";
break;
}
std::string name("");
for (int i = 0; i < fSz; i++)
name += char(input->readULong(1));
info.m_actionString=name;
break;
}
default:
break;
}
extraN = int(endPos-input->tell())/2;
if (extraN==1) {
val =static_cast<int>(input->readLong(2));
if (val==0)
info.m_print = false;
else if (val==1)
info.m_print = true;
else if (val) {
f << "#print=" << val << ",";
}
}
else {
for (int i = 0; i < extraN; i++) { // g0=0|1
val =static_cast<int>(input->readLong(2));
if (val)
f << "#g" << i << "=" << val << ",";
}
}
info.m_extra=f.str();
m_state->m_idPictInfoMap[entry.id()]=info;
f.str("");
f << "Entries(PctInfo)[" << entry.type() << "-" << entry.id() << "]:" << info;
if (input->tell()!=entry.end())
ascFile.addDelimiter(input->tell(),'|');
ascFile.addPos(pos-4);
ascFile.addNote(f.str().c_str());
return true;
}
////////////////////////////////////////////////////////////
// read some unknown zone
////////////////////////////////////////////////////////////
bool DocMkrParser::readSTwD(MWAWEntry const &entry)
{
if (!entry.valid() || entry.length()<10) {
MWAW_DEBUG_MSG(("DocMkrText::readSTwD: the entry seems very short\n"));
return false;
}
entry.setParsed(true);
long pos = entry.begin();
MWAWInputStreamPtr input = rsrcInput();
libmwaw::DebugFile &ascFile = rsrcAscii();
input->seek(pos, librevenge::RVNG_SEEK_SET);
libmwaw::DebugStream f;
f << "Entries(STwD)[" << entry.type() << "-" << entry.id() << "]:";
int val;
for (int i=0; i < 2; i++) { // f0=2, f1=1|2
val =static_cast<int>(input->readLong(2));
if (val)
f << "f" << i << "=" << val << ",";
}
auto flag =static_cast<int>(input->readLong(2)); // 320|7d0|1388 ?
f << "fl=" << std::hex << flag << std::dec << ",";
f << "dim=" << static_cast<int>(input->readLong(2)) << ","; // 0x1Fa|0x200
for (int i=0; i < 2; i++) { // f0=1, f1=0|1
val =static_cast<int>(input->readLong(1));
if (val)
f << "f" << i+2 << "=" << val << ",";
}
f << "],";
if (input->tell()!=entry.end())
ascFile.addDelimiter(input->tell(),'|');
ascFile.addPos(pos-4);
ascFile.addNote(f.str().c_str());
return true;
}
bool DocMkrParser::readXtr2(MWAWEntry const &entry)
{
if (!entry.valid() || entry.length()<1) {
MWAW_DEBUG_MSG(("DocMkrText::readXtr2: the entry seems very short\n"));
return false;
}
entry.setParsed(true);
long pos = entry.begin();
MWAWInputStreamPtr input = rsrcInput();
libmwaw::DebugFile &ascFile = rsrcAscii();
input->seek(pos, librevenge::RVNG_SEEK_SET);
libmwaw::DebugStream f;
f << "Entries(Xtr2)[" << entry.type() << "-" << entry.id() << "]:";
int N=1;
if (entry.length() != 1) {
MWAW_DEBUG_MSG(("DocMkrText::readXtr2: find more than one flag\n"));
N = entry.length()>20 ? 20 : int(entry.length());
}
// f0=79|a8|b9|99
for (int i=0; i < N; i++) {
auto val =static_cast<int>(input->readULong(1));
if (val)
f << "f" << i << "=" << std::hex << val << std::dec << ",";
}
if (input->tell()!=entry.end())
ascFile.addDelimiter(input->tell(),'|');
ascFile.addPos(pos-4);
ascFile.addNote(f.str().c_str());
return true;
}
////////////////////////////////////////////////////////////
// read the header
////////////////////////////////////////////////////////////
bool DocMkrParser::checkHeader(MWAWHeader *header, bool /*strict*/)
{
*m_state = DocMkrParserInternal::State();
/** no data fork, may be ok, but this means
that the file contains no text, so... */
MWAWInputStreamPtr input = getInput();
if (!input || !getRSRCParser())
return false;
if (input->hasDataFork()) {
MWAW_DEBUG_MSG(("DocMkrParser::checkHeader: find a datafork, odd!!!\n"));
}
MWAWRSRCParser::Version vers;
// read the Docmaker version
int docmakerVersion = 1;
MWAWEntry entry = getRSRCParser()->getEntry("vers", 2);
if (entry.valid() && getRSRCParser()->parseVers(entry, vers))
docmakerVersion = vers.m_majorVersion;
else {
MWAW_DEBUG_MSG(("DocMkrParser::checkHeader: can not find the DocMaker version\n"));
}
setVersion(docmakerVersion);
if (header)
header->reset(MWAWDocument::MWAW_T_DOCMAKER, version());
return true;
}
// vim: set filetype=cpp tabstop=2 shiftwidth=2 cindent autoindent smartindent noexpandtab: