/* -*- 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 <cstring>
#include <iomanip>
#include <iostream>
#include <limits>
#include <map>
#include <sstream>
#include <librevenge/librevenge.h>
#include "MWAWTextListener.hxx"
#include "MWAWDebug.hxx"
#include "MWAWFont.hxx"
#include "MWAWFontConverter.hxx"
#include "MWAWList.hxx"
#include "MWAWPageSpan.hxx"
#include "MWAWParagraph.hxx"
#include "MWAWPictData.hxx"
#include "MWAWPosition.hxx"
#include "MWAWRSRCParser.hxx"
#include "MWAWSubDocument.hxx"
#include "MoreParser.hxx"
#include "MoreText.hxx"
/** Internal: the structures of a MoreText */
namespace MoreTextInternal
{
////////////////////////////////////////
//! Internal: the paragraph of a MoreText
struct Paragraph final : public MWAWParagraph {
//! constructor
Paragraph()
: MWAWParagraph()
, m_listType(0)
, m_customListLevel()
, m_pageBreak(false)
, m_keepOutlineTogether(false)
{
m_marginsFromParent[0]=0.3;
m_marginsFromParent[1]=0;
}
//! destructor
~Paragraph() final;
//! set the left margin in inch
void setLeftMargin(double margin, bool fromParent)
{
if (fromParent) {
m_marginsFromParent[0]=margin;
m_margins[1]=0;
}
else {
m_marginsFromParent[0]=-100;
m_margins[1]=margin;
}
}
//! set the right margin in inch
void setRightMargin(double margin, bool fromParent)
{
if (fromParent) {
m_marginsFromParent[1]=margin;
m_margins[2]=0;
}
else {
m_marginsFromParent[1]=-100;
m_margins[2]=margin;
}
}
//! update the paragraph to obtain the final paragraph
void updateToFinalState(MWAWParagraph const &parent, int level, MWAWListManager &listManager)
{
bool leftUseParent=m_marginsFromParent[0]>-10;
if (leftUseParent)
m_margins[1]=*parent.m_margins[1]+m_marginsFromParent[0];
if (m_marginsFromParent[1]>-10)
m_margins[2]=*parent.m_margins[2]+m_marginsFromParent[1];
if (level<0)
return;
MWAWListLevel listLevel;
switch (m_listType) {
case 1: // leader
listLevel.m_type = MWAWListLevel::BULLET;
listLevel.m_bullet = "+"; // in fact + + and -
break;
case 2: // hardvard
listLevel.m_suffix = (level <= 3) ? "." : ")";
if (level == 1) listLevel.m_type = MWAWListLevel::UPPER_ROMAN;
else if (level == 2) listLevel.m_type = MWAWListLevel::UPPER_ALPHA;
else if (level == 3) listLevel.m_type = MWAWListLevel::DECIMAL;
else if (level == 4) listLevel.m_type = MWAWListLevel::LOWER_ALPHA;
else if ((level%3)==2) {
listLevel.m_prefix = "(";
listLevel.m_type = MWAWListLevel::DECIMAL;
}
else if ((level%3)==0) {
listLevel.m_prefix = "(";
listLevel.m_type = MWAWListLevel::LOWER_ALPHA;
}
else
listLevel.m_type = MWAWListLevel::LOWER_ROMAN;
break;
case 3: // numeric
listLevel.m_type = MWAWListLevel::DECIMAL;
listLevel.m_suffix = ".";
break;
case 4: // legal
listLevel.m_type = MWAWListLevel::DECIMAL;
listLevel.m_numBeforeLabels = level-1;
listLevel.m_suffix = ".";
listLevel.m_labelWidth = 0.2*level;
break;
case 5: // bullets
listLevel.m_type = MWAWListLevel::BULLET;
libmwaw::appendUnicode(0x2022, listLevel.m_bullet);
break;
case 0: // none
return;
default:
if (m_listType<11)
return;
listLevel=m_customListLevel;
break;
}
m_listLevelIndex = level+1;
std::shared_ptr<MWAWList> parentList;
if (*parent.m_listId>=0)
parentList=listManager.getList(*parent.m_listId);
auto list=listManager.getNewList(parentList, level+1, listLevel);
if (list)
m_listId=list->getId();
else
m_listLevel=listLevel;
m_margins[1]=m_margins[1].get()-listLevel.m_labelWidth;
}
//! the left and right margins from parent in inches
double m_marginsFromParent[2];
//! the list type (0: none, 1: leader, ...)
int m_listType;
//! a custom list level ( only defined if m_listType>=0xb)
MWAWListLevel m_customListLevel;
//! true if we need to add a page break before
bool m_pageBreak;
//! true if we need to keep outline together
bool m_keepOutlineTogether;
};
Paragraph::~Paragraph()
{
}
////////////////////////////////////////
//! Internal: the outline data of a MoreText
struct Outline {
//! constructor
Outline()
{
// set to default value
for (auto &font : m_fonts) font=MWAWFont(3,12);
m_paragraphs[0].m_listType=1;
}
//! the paragraphs : organizer, slide, tree, unknowns
Paragraph m_paragraphs[4];
//! the fonts : organizer, slide, tree unknowns
MWAWFont m_fonts[4];
};
////////////////////////////////////////
//! Internal and low level: the outline modifier header of a MoreText
struct OutlineMod {
//! constructor
OutlineMod()
: m_type(-1)
, m_flags(0)
, m_entry()
, m_extra("")
{
for (auto &unkn : m_unknowns) unkn=0;
}
//! returns the data id to change in Outline
int getModId() const
{
if (m_unknowns[0] || (m_flags&0xF)!= 1)
return 3;
switch (m_flags>>4) {
case 1:
return 0;
case 2:
return 1;
case 4:
return 2;
default:
break;
}
return 3;
}
//! operator<<
friend std::ostream &operator<<(std::ostream &o, OutlineMod const &head)
{
switch (head.m_flags>>4) {
case 1: // organizer
break;
case 2:
o << "slide,";
break;
case 4:
o << "tree,";
break;
default:
o << "##wh=" << std::hex << (head.m_flags>>4) << std::dec << ",";
break;
}
switch (head.m_type) {
case 0x301:
o << "font,";
break;
case 0x402:
o << "fSize,";
break;
case 0x603:
o << "fFlags,";
break;
case 0x804:
o << "fColor,";
break;
case 0xa05:
o << "interline,";
break;
case 0xc0f:
o << "firstIndent,";
break;
case 0xf07:
o << "tabs,";
break;
case 0x1006:
o << "justify,";
break;
case 0x1208:
o << "bef/afterspace,";
break;
case 0x1409:
o << "lMargin,";
break;
case 0x160a:
o << "rMargin,";
break;
case 0x190b:
o << "list[type],";
break;
case 0x1a0c:
o << "break,";
break;
case 0x1c0d:
o << "keepline,";
break;
case 0x1e0e:
o << "keep[outline],";
break;
case -1:
break;
default:
// 2914|2b15|3319|3b1c|6532|6934|7137|773a|7b3c -> backside|backpattern
o << "type=" << std::hex << head.m_type << std::dec << ",";
break;
}
for (int i=0; i<2; i++) { // unkn0=0|1|2(related to clone?), unkn1=0|1|999
if (head.m_unknowns[i])
o << "unkn" << i << "=" << head.m_unknowns[i] << ",";
}
if (head.m_flags&0xF)
o << "fl=" << std::hex << (head.m_flags&0xF) << std::dec << ",";
if (head.m_entry.valid())
o << std::hex << head.m_entry.begin() << "<->" << head.m_entry.end() << std::dec << ",";
o << head.m_extra;
return o;
}
//! the type
int m_type;
//! the flag
int m_flags;
//! the data entry
MWAWEntry m_entry;
//! some unknown flags
int m_unknowns[2];
//! extra data
std::string m_extra;
};
////////////////////////////////////////
//! Internal: the comment data of a MoreText
struct Comment {
//! constructor
Comment()
: m_entry()
, m_extra("")
{
}
//! operator<<
friend std::ostream &operator<<(std::ostream &o, Comment const &comment)
{
o << comment.m_extra;
return o;
}
//! the text entry
MWAWEntry m_entry;
//! extra data
std::string m_extra;
};
////////////////////////////////////////
//! Internal: the topic data of a MoreText
struct Topic {
//! an enum used to define the different type of data attached to a topic
enum AttachementType { AOutline=0, AComment, ASpeakerNote };
//! constructor
Topic()
: m_entry()
, m_level(0)
, m_isCloned(false)
, m_cloneId(-1)
, m_numPageBreak(-1)
, m_isStartSlide(false)
, m_extra("")
{
for (auto &hasData : m_hasList) hasData=false;
for (auto &attachData : m_attachList) attachData=-1;
}
//! operator<<
friend std::ostream &operator<<(std::ostream &o, Topic const &topic)
{
if (topic.m_level>0)
o << "level=" << topic.m_level << ",";
if (topic.m_hasList[AOutline])
o << "outline,";
if (topic.m_hasList[AComment])
o << "comment,";
if (topic.m_hasList[ASpeakerNote])
o << "speakerNote,";
if (topic.m_isCloned)
o << "cloned,";
if (topic.m_cloneId >= 0)
o << "cloneId=" << topic.m_cloneId << ",";
if (topic.m_isStartSlide)
o << "newSlide,";
o << topic.m_extra;
return o;
}
//! the text entry
MWAWEntry m_entry;
//! the topic level
int m_level;
//! true if the entry is cloned
bool m_isCloned;
//! if not 0, indicate that we must cloned the cloneId^th clone
int m_cloneId;
//! a list of boolean use to note if a topic is associated with a Outline, ...
bool m_hasList[3];
//! a list of id to retrieve the attachment
int m_attachList[3];
//! the number of pages in the sub list
int m_numPageBreak;
//! true if we start a new slide
bool m_isStartSlide;
//! extra data
std::string m_extra;
};
////////////////////////////////////////
//! Internal: the state of a MoreText
struct State {
//! constructor
State()
: m_version(-1)
, m_topicList()
, m_commentList()
, m_speakerList()
, m_outlineList()
, m_actualComment(0)
, m_actualSpeaker(0)
, m_actualOutline(0)
, m_numPages(-1)
, m_actualPage(1)
{
}
//! the file version
mutable int m_version;
//! the topic list
std::vector<Topic> m_topicList;
//! the header/footer/comment list
std::vector<Comment> m_commentList;
//! the speaker note list
std::vector<MWAWEntry> m_speakerList;
//! the outline list
std::vector<Outline> m_outlineList;
//! the actual comment
int m_actualComment;
//! the actual speaker note
int m_actualSpeaker;
//! the actual outline
int m_actualOutline;
int m_numPages /* the number of pages */, m_actualPage /* the actual page */;
};
////////////////////////////////////////
//! Internal: the subdocument of a MoreText
class SubDocument final : public MWAWSubDocument
{
public:
SubDocument(MoreText &pars, MWAWInputStreamPtr const &input, int zId, int what)
: MWAWSubDocument(pars.m_mainParser, input, MWAWEntry())
, m_textParser(&pars)
, m_id(zId)
, m_what(what)
{
}
//! destructor
~SubDocument() final {}
//! operator!=
bool operator!=(MWAWSubDocument const &doc) const final;
//! the parser function
void parse(MWAWListenerPtr &listener, libmwaw::SubDocumentType type) final;
protected:
/** the text parser */
MoreText *m_textParser;
//! the subdocument id
int m_id;
//! a int to know what to send 0: header/footer, 1: comment, 2:note
int m_what;
private:
SubDocument(SubDocument const &orig) = delete;
SubDocument &operator=(SubDocument const &orig) = delete;
};
void SubDocument::parse(MWAWListenerPtr &listener, libmwaw::SubDocumentType /*type*/)
{
if (!listener.get()) {
MWAW_DEBUG_MSG(("MoreTextInternal::SubDocument::parse: no listener\n"));
return;
}
if (!m_textParser) {
MWAW_DEBUG_MSG(("MoreTextInternal::SubDocument::parse: no parser\n"));
return;
}
long pos = m_input->tell();
switch (m_what) {
case 0: {
std::vector<MWAWParagraph> paraStack;
m_textParser->sendTopic(m_id,0,paraStack);
break;
}
case 1:
m_textParser->sendComment(m_id);
break;
case 2:
m_textParser->sendSpeakerNote(m_id);
break;
default:
MWAW_DEBUG_MSG(("SubDocument::parse: unknow value in m_what\n"));
break;
}
m_input->seek(pos, librevenge::RVNG_SEEK_SET);
}
bool SubDocument::operator!=(MWAWSubDocument const &doc) const
{
if (MWAWSubDocument::operator!=(doc)) return true;
auto const *sDoc = dynamic_cast<SubDocument const *>(&doc);
if (!sDoc) return true;
if (m_textParser != sDoc->m_textParser) return true;
if (m_id != sDoc->m_id) return true;
return m_what != sDoc->m_what;
}
}
////////////////////////////////////////////////////////////
// constructor/destructor, ...
////////////////////////////////////////////////////////////
MoreText::MoreText(MoreParser &parser)
: m_parserState(parser.getParserState())
, m_state(new MoreTextInternal::State)
, m_mainParser(&parser)
{
}
MoreText::~MoreText()
{
}
int MoreText::version() const
{
if (m_state->m_version < 0)
m_state->m_version = m_parserState->m_version;
return m_state->m_version;
}
int MoreText::numPages() const
{
if (m_state->m_numPages >= 0)
return m_state->m_numPages;
MWAW_DEBUG_MSG(("MoreText::createZones is not called\n"));
const_cast<MoreText *>(this)->createZones();
return m_state->m_numPages;
}
std::shared_ptr<MWAWSubDocument> MoreText::getHeaderFooter(bool header)
{
std::shared_ptr<MWAWSubDocument> res;
size_t id=header ? 1 : 2;
if (id >= m_state->m_topicList.size())
return res;
auto const &topic= m_state->m_topicList[id];
// check if the content is empty
int comment=topic.m_attachList[MoreTextInternal::Topic::AComment];
if (comment < 0 || comment >= int(m_state->m_commentList.size()))
return res;
if (m_state->m_commentList[size_t(comment)].m_entry.length()<=4)
return res;
res.reset(new MoreTextInternal::SubDocument(*this, m_parserState->m_input, int(id), 0));
return res;
}
////////////////////////////////////////////////////////////
// Intermediate level
////////////////////////////////////////////////////////////
//
// find/send the different zones
//
bool MoreText::createZones()
{
if (m_state->m_topicList.empty())
return false;
// first create the list of cloned topic
std::vector<int> clonedList;
for (size_t i=0; i < m_state->m_topicList.size(); i++) {
auto const &topic=m_state->m_topicList[i];
if (topic.m_isCloned)
clonedList.push_back(int(i));
}
// now associated each topic with its outline, its comment, its original topic (if clone)
auto numCloned=int(clonedList.size());
size_t actAttach[3]= {0,0,0};
size_t numAttach[3]= {0,0,0};
numAttach[MoreTextInternal::Topic::AOutline]=m_state->m_outlineList.size();
numAttach[MoreTextInternal::Topic::AComment]=m_state->m_commentList.size();
numAttach[MoreTextInternal::Topic::ASpeakerNote]=m_state->m_speakerList.size();
int n=0;
for (auto &topic : m_state->m_topicList) {
++n;
for (int j=0; j < 3; j++) {
if (!topic.m_hasList[j])
continue;
if (actAttach[j] >= numAttach[j]) {
MWAW_DEBUG_MSG(("MoreText::createZones: can not find attach-%d for topic %d\n", j, n-1));
continue;
}
topic.m_attachList[j]=int(actAttach[j]++);
switch (j) {
case MoreTextInternal::Topic::AOutline:
break;
case MoreTextInternal::Topic::AComment: // no need to add empty comment
if (m_state->m_commentList[size_t(topic.m_attachList[j])].m_entry.length() <= 4)
topic.m_attachList[j]=-1;
break;
case MoreTextInternal::Topic::ASpeakerNote: // no need to add empty speaker note
if (m_state->m_speakerList[size_t(topic.m_attachList[j])].length() <= 4)
topic.m_attachList[j]=-1;
break;
default:
break;
}
}
int cloneId=topic.m_cloneId;
if (cloneId < 0)
continue;
if (cloneId==0 || cloneId > numCloned) {
MWAW_DEBUG_MSG(("MoreText::createZones: can not find original for topic %d\n", n-1));
topic.m_cloneId=-1;
}
else
topic.m_cloneId=clonedList[size_t(cloneId-1)];
}
// now check that we have no loop
for (size_t i=0; i < m_state->m_topicList.size(); i++) {
auto &topic=m_state->m_topicList[i];
if (topic.m_cloneId<0)
continue;
std::set<size_t> parent;
checkTopicList(i, parent);
}
// now compute the number of page
int nPages = 1;
for (auto const &topic : m_state->m_topicList) {
if (topic.m_numPageBreak >= 0) {
nPages+=topic.m_numPageBreak;
}
int outId=topic.m_attachList[MoreTextInternal::Topic::AOutline];
if (outId<0) continue;
if (m_state->m_outlineList[size_t(outId)].m_paragraphs[0].m_pageBreak)
nPages++;
}
m_state->m_actualPage = 1;
m_state->m_numPages = nPages;
return true;
}
int MoreText::getLastTopicChildId(int tId) const
{
size_t numTopic=m_state->m_topicList.size();
if (tId < 0||tId>= int(numTopic)) {
MWAW_DEBUG_MSG(("MoreText::getLastTopicChildId: can not find topic %d\n", tId));
return tId;
}
auto p=size_t(tId);
int level=m_state->m_topicList[p].m_level;
while (p+1<numTopic && m_state->m_topicList[p].m_level>level)
p++;
return int(p);
}
int MoreText::checkTopicList(size_t tId, std::set<size_t> &parent)
{
size_t numTopic=m_state->m_topicList.size();
if (tId>=numTopic) {
MWAW_DEBUG_MSG(("MoreText::checkTopicList: can not find topic %d\n", int(tId)));
return 0;
}
if (parent.find(tId)!=parent.end()) {
MWAW_DEBUG_MSG(("MoreText::checkTopicList: repairing fails\n"));
throw libmwaw::ParseException();
}
parent.insert(tId);
auto &topic=m_state->m_topicList[tId];
int nPages=0;
int outId=topic.m_attachList[MoreTextInternal::Topic::AOutline];
if (outId>=0) {
if (m_state->m_outlineList[size_t(outId)].m_paragraphs[0].m_pageBreak)
nPages++;
}
auto id=int(tId);
if (topic.m_cloneId>=0) {
if (parent.find(size_t(topic.m_cloneId))!=parent.end()) {
MWAW_DEBUG_MSG(("MoreText::checkTopicList: find a loop, remove a clone\n"));
topic.m_cloneId=-1;
parent.erase(tId);
return 0;
}
id = topic.m_cloneId;
parent.insert(size_t(id));
}
int lastTId=getLastTopicChildId(id);
for (int i=id+1; i<=lastTId; i++)
nPages+=checkTopicList(size_t(i), parent);
topic.m_numPageBreak=nPages;
parent.erase(tId);
if (id!=int(tId))
parent.erase(size_t(tId));
return nPages;
}
// try read to the file text position
bool MoreText::readTopic(MWAWEntry const &entry)
{
if (!entry.valid() || (entry.length()%10)) {
MWAW_DEBUG_MSG(("MoreText::readTopic: the entry is bad\n"));
return false;
}
// topic 0-2: ?, header,footer, topic 3: title, after document
long pos = entry.begin();
MWAWInputStreamPtr &input= m_parserState->m_input;
libmwaw::DebugFile &ascFile = m_parserState->m_asciiFile;
libmwaw::DebugStream f;
input->seek(pos, librevenge::RVNG_SEEK_SET);
entry.setParsed(true);
ascFile.addPos(pos);
ascFile.addNote("Entries(Topic)");
auto N=int(entry.length()/10);
for (int i=0; i < N; i++) {
pos=input->tell();
MoreTextInternal::Topic topic;
f.str("");
topic.m_level = static_cast<int>(input->readLong(2));
bool isAClone=false;
auto flag = static_cast<int>(input->readULong(2)); // some flag ?
if ((flag&1)==0) f << "hidden,";
if (flag&4) f << "marked,";
if (flag&0x10) topic.m_isCloned=true;
if (flag&0x20) isAClone=true;
if (flag&0x40) {
topic.m_hasList[MoreTextInternal::Topic::ASpeakerNote]=true;
f << "S" << m_state->m_actualSpeaker++ << ",";
}
if (flag&0x80) {
topic.m_hasList[MoreTextInternal::Topic::AComment]=true;
f << "C" << m_state->m_actualComment++ << ",";
}
if (flag&0x400) f << "showComment,";
if (flag&0x2000) topic.m_isStartSlide=true;
if (flag&0x8000) {
topic.m_hasList[MoreTextInternal::Topic::AOutline]=true;
f << "O" << m_state->m_actualOutline++ << ",";
}
// find only bits: 5208
flag &= 0x5B4A;
if (flag) f << "fl=" << std::hex << flag << std::dec << ",";
long fPos = input->readLong(4);
if (isAClone)
topic.m_cloneId=static_cast<int>(fPos);
else {
f << "pos=" << std::hex << fPos << std::dec << ",";
topic.m_entry.setBegin(fPos);
if (!m_mainParser->checkAndFindSize(topic.m_entry)) {
MWAW_DEBUG_MSG(("MoreText::readTopic: can not read a text position\n"));
f << "###";
topic.m_entry = MWAWEntry();
}
}
auto val = static_cast<int>(input->readLong(2)); // a small number 1 or 2
if (val)
f << "f1=" << val << ",";
topic.m_extra=f.str();
m_state->m_topicList.push_back(topic);
f.str("");
f << "Topic-" << i << ":" << topic;
ascFile.addPos(pos);
ascFile.addNote(f.str().c_str());
input->seek(pos+10, librevenge::RVNG_SEEK_SET);
}
return true;
}
bool MoreText::readComment(MWAWEntry const &entry)
{
if (!entry.valid() || (entry.length()%8)) {
MWAW_DEBUG_MSG(("MoreText::readComment: the entry is bad\n"));
return false;
}
// comment0? comment1: header, comment2: footer
long pos = entry.begin();
MWAWInputStreamPtr &input= m_parserState->m_input;
libmwaw::DebugFile &ascFile = m_parserState->m_asciiFile;
libmwaw::DebugStream f;
input->seek(pos, librevenge::RVNG_SEEK_SET);
entry.setParsed(true);
ascFile.addPos(pos);
ascFile.addNote("Entries(Comment)");
auto N=int(entry.length()/8);
for (int i=0; i < N; i++) {
pos=input->tell();
MoreTextInternal::Comment comment;
f.str("");
long fPos = input->readLong(4);
f << "pos=" << std::hex << fPos << std::dec << ",";
comment.m_entry.setBegin(fPos);
if (!m_mainParser->checkAndFindSize(comment.m_entry)) {
MWAW_DEBUG_MSG(("MoreText::readComment: can not read a file position\n"));
f << "###";
comment.m_entry.setLength(0);
}
auto val = static_cast<int>(input->readLong(2)); // always 4 ?
if (val != 4)
f << "f0=" << val << ",";
val = static_cast<int>(input->readULong(2)); // some flag ? find 0x3333 0x200d ...
if (val) f << "fl=" << std::hex << val << std::dec << ",";
comment.m_extra=f.str();
m_state->m_commentList.push_back(comment);
f.str("");
f << "Comment-C" << i << ":" << comment;
ascFile.addPos(pos);
ascFile.addNote(f.str().c_str());
input->seek(pos+8, librevenge::RVNG_SEEK_SET);
}
return true;
}
bool MoreText::readSpeakerNote(MWAWEntry const &entry)
{
if (!entry.valid() || (entry.length()%4)) {
MWAW_DEBUG_MSG(("MoreText::readSpeakerNote: the entry is bad\n"));
return false;
}
long pos = entry.begin();
MWAWInputStreamPtr &input= m_parserState->m_input;
libmwaw::DebugFile &ascFile = m_parserState->m_asciiFile;
libmwaw::DebugStream f;
input->seek(pos, librevenge::RVNG_SEEK_SET);
entry.setParsed(true);
f << "Entries(SpeakerNote):";
auto N=int(entry.length()/4);
for (int i=0; i < N; i++) {
long fPos = input->readLong(4);
f << "S" << i << ":pos=" << std::hex << fPos << std::dec << ",";
MWAWEntry tEntry;
tEntry.setBegin(fPos);
if (!m_mainParser->checkAndFindSize(tEntry)) {
MWAW_DEBUG_MSG(("MoreText::readSpeakerNote: can not read a file position\n"));
f << "###";
tEntry.setLength(0);
}
m_state->m_speakerList.push_back(tEntry);
}
ascFile.addPos(pos);
ascFile.addNote(f.str().c_str());
return true;
}
//
// send the text
//
bool MoreText::sendMainText()
{
libmwaw::DebugFile &ascFile = m_parserState->m_asciiFile;
libmwaw::DebugStream f;
std::vector<MWAWParagraph> paraStack;
/* skip 0: unknown, 1: header(comment), 2: footer(comment), 3: title?, after text */
for (size_t i=4; i < m_state->m_topicList.size(); i++) {
auto const &topic = m_state->m_topicList[i];
MWAWEntry const &entry=topic.m_entry;
if (!entry.valid()) { // a clone ?
sendTopic(int(i),0,paraStack);
continue;
}
ascFile.addPos(entry.end());
ascFile.addNote("_");
if (sendTopic(int(i),0,paraStack))
continue;
ascFile.addPos(entry.end());
ascFile.addNote("_");
f.str("");
f << "Topic-" << i << "[data]:";
ascFile.addPos(entry.begin());
ascFile.addNote(f.str().c_str());
}
return true;
}
bool MoreText::sendComment(int cId)
{
MWAWTextListenerPtr listener=m_parserState->m_textListener;
if (!listener) {
MWAW_DEBUG_MSG(("MoreText::sendComment: can not find a listener!"));
return true;
}
if (cId < 0 || cId >= int(m_state->m_commentList.size())) {
MWAW_DEBUG_MSG(("MoreText::sendComment: can not find the comment %d!", cId));
return false;
}
return sendText(m_state->m_commentList[size_t(cId)].m_entry, MWAWFont(3,12));
}
bool MoreText::sendSpeakerNote(int nId)
{
MWAWTextListenerPtr listener=m_parserState->m_textListener;
if (!listener) {
MWAW_DEBUG_MSG(("MoreText::sendSpeakerNote: can not find a listener!"));
return true;
}
if (nId < 0 || nId >= int(m_state->m_speakerList.size())) {
MWAW_DEBUG_MSG(("MoreText::sendSpeakerNote: can not find the speaker note %d!", nId));
return false;
}
return sendText(m_state->m_speakerList[size_t(nId)], MWAWFont(3,12));
}
bool MoreText::sendTopic(int tId, int dLevel, std::vector<MWAWParagraph> ¶Stack)
{
MWAWTextListenerPtr listener=m_parserState->m_textListener;
if (!listener) {
MWAW_DEBUG_MSG(("MoreText::sendTopic: can not find a listener!"));
return true;
}
if (tId < 0 || tId >= int(m_state->m_topicList.size())) {
MWAW_DEBUG_MSG(("MoreText::sendTopic: can not find the topic note %d!", tId));
return false;
}
auto const &topic=m_state->m_topicList[size_t(tId)];
MWAWEntry entry=topic.m_entry;
int level=topic.m_level+dLevel;
if (level < 0) {
if (tId>=4) {
MWAW_DEBUG_MSG(("MoreText::sendTopic: oops level is negatif!\n"));
}
level=0;
}
// data to send clone child
int actId=tId, lastId = tId, cloneDLevel=0;
if (tId==1||tId==2) {
// header/footer: the data are in the comment...
int comment=topic.m_attachList[MoreTextInternal::Topic::AComment];
if (comment<0||comment>=int(m_state->m_commentList.size()))
return false;
entry=m_state->m_commentList[size_t(comment)].m_entry;
}
else if (topic.m_cloneId>=0 && topic.m_cloneId < static_cast<int>(m_state->m_topicList.size())) {
auto const &cTopic = m_state->m_topicList[size_t(topic.m_cloneId)];
entry=cTopic.m_entry;
actId = topic.m_cloneId;
lastId = getLastTopicChildId(topic.m_cloneId);
cloneDLevel = cTopic.m_level-level;;
}
if (!entry.valid())
return false;
// retrieve the ouline
MWAWFont font(3,12);
MoreTextInternal::Paragraph para;
int outl=topic.m_attachList[MoreTextInternal::Topic::AOutline];
if (outl>=0 && outl < static_cast<int>(m_state->m_outlineList.size())) {
auto const &outline=m_state->m_outlineList[size_t(outl)];
if (outline.m_paragraphs[0].m_pageBreak)
m_mainParser->newPage(++m_state->m_actualPage);
para=outline.m_paragraphs[0];
font = outline.m_fonts[0];
}
else if (tId>=4) {
/* default: leader is default for a paragraph
note: sometimes, some small level are bold by default, I do not understand why ? */
para.m_listType=1;
}
if (tId==1||tId==2) // force no list in header footer
para.m_listType=0;
if (level >= int(paraStack.size()))
paraStack.resize(size_t(level+1));
if (level>0)
para.updateToFinalState(paraStack[size_t(level-1)], level,
*m_parserState->m_listManager);
else
para.updateToFinalState(MWAWParagraph(),0, *m_parserState->m_listManager);
if (level>=0)
paraStack[size_t(level)]=para;
listener->setParagraph(para);
bool ok=sendText(entry, font);
if (tId==1||tId==2)
return true;
// send potential comment and speakernote
int comment=topic.m_attachList[MoreTextInternal::Topic::AComment];
if (comment>=0) {
MWAWSubDocumentPtr doc(new MoreTextInternal::SubDocument(*this, m_parserState->m_input, comment, 1));
listener->insertComment(doc);
}
int speaker=topic.m_attachList[MoreTextInternal::Topic::ASpeakerNote];
if (speaker>=0) {
MWAWSubDocumentPtr doc(new MoreTextInternal::SubDocument(*this, m_parserState->m_input, speaker, 2));
listener->insertComment(doc);
}
listener->insertEOL();
for (int i=actId+1; i <= lastId; i++)
sendTopic(i, dLevel+cloneDLevel, paraStack);
return ok;
}
bool MoreText::sendText(MWAWEntry const &entry, MWAWFont const &font)
{
MWAWTextListenerPtr listener=m_parserState->m_textListener;
if (!listener) {
MWAW_DEBUG_MSG(("MoreText::sendText: can not find a listener!"));
return true;
}
MWAWInputStreamPtr &input= m_parserState->m_input;
libmwaw::DebugFile &ascFile = m_parserState->m_asciiFile;
libmwaw::DebugStream f;
long pos = entry.begin();
long endPos = entry.end();
if (entry.length()==4) { // no text, we can stop here
ascFile.addPos(pos);
ascFile.addNote("Entries(Text):");
ascFile.addPos(entry.end());
ascFile.addNote("_");
return true;
}
if (entry.length()<4) {
MWAW_DEBUG_MSG(("MoreText::sendText: the entry is bad\n"));
return false;
}
input->seek(pos+4, librevenge::RVNG_SEEK_SET);
listener->setFont(font);
f << "Entries(Text):";
int val;
MWAWFont ft(font);
MWAWColor defCol;
font.getColor(defCol);
uint32_t defFlags=font.flags();
bool defHasUnderline=font.getUnderline().isSet();
listener->setFont(ft);
while (!input->isEnd()) {
long actPos = input->tell();
if (actPos >= endPos)
break;
auto c=static_cast<unsigned char>(input->readULong(1));
if (c!=0x1b) {
listener->insertCharacter(c);
f << c;
continue;
}
if (actPos+1 >= endPos) {
f << "@[#]";
MWAW_DEBUG_MSG(("MoreText::sendText: text end by 0x1b\n"));
continue;
}
auto fld=static_cast<int>(input->readULong(1));
bool sendFont=true;
switch (fld) {
case 0x9:
listener->insertTab();
f << "\t";
break;
case 0xd: // EOL in header/footer
listener->insertEOL();
f << char(0xd);
break;
case 0x2d: // geneva
ft.setId(font.id());
sendFont=false;
f << "@[fId=def]";
break;
case 0x2e: // 12
ft.setSize(font.size());
sendFont=false;
f << "@[fSz=def]";
break;
case 0x2f: // black
ft.setColor(defCol);
sendFont=false;
f << "@[fCol=def]";
break;
case 0x30: // font
if (actPos+4+2 > endPos) {
f << "@[#fId]";
MWAW_DEBUG_MSG(("MoreText::sendText: field font seems too short\n"));
break;
}
val = static_cast<int>(input->readULong(2));
if (!(val&0x8000)) {
MWAW_DEBUG_MSG(("MoreText::sendText: field fId: unexpected id\n"));
f << "@[#fId]";
input->seek(-2, librevenge::RVNG_SEEK_CUR);
break;
}
f << "@[fId=" << (val&0x7FFF) << "]";
ft.setId(val&0x7FFF);
sendFont = false;
val = static_cast<int>(input->readULong(2));
if (val!=0x1b30) {
MWAW_DEBUG_MSG(("MoreText::sendText: field fId: unexpected end field\n"));
f << "###";
input->seek(-2, librevenge::RVNG_SEEK_CUR);
break;
}
break;
case 0x31:
if (actPos+4+2 > endPos) {
f << "@[#fSz]";
MWAW_DEBUG_MSG(("MoreText::sendText: field fSz seems too short\n"));
break;
}
val = static_cast<int>(input->readLong(2));
f << "@[fSz=" << val << "]";
if (val <= 0) {
MWAW_DEBUG_MSG(("MoreText::sendText: field fSz seems bad\n"));
f << "###";
}
else {
ft.setSize(float(val));
sendFont = false;
}
val = static_cast<int>(input->readULong(2));
if (val!=0x1b31) {
MWAW_DEBUG_MSG(("MoreText::sendText: field fSz: unexpected end field\n"));
f << "###";
input->seek(-2, librevenge::RVNG_SEEK_CUR);
break;
}
break;
case 0x38: {
if (actPos+4+10 > endPos) {
f << "@[#fCol]";
MWAW_DEBUG_MSG(("MoreText::sendText: field fCol seems too short\n"));
break;
}
uint16_t values[5];
for (auto &v : values) v=static_cast<uint16_t>(input->readULong(2));
if (values[0]!=0xe || values[4]!=0xe) {
MWAW_DEBUG_MSG(("MoreText::sendText: field fCol: color sep seems bad\n"));
f << "@[fCol###]";
}
else {
MWAWColor col(static_cast<unsigned char>(values[1]>>8),
static_cast<unsigned char>(values[2]>>8),
static_cast<unsigned char>(values[3]>>8));
ft.setColor(col);
sendFont = false;
f << "@[fCol=" << col << "]";
}
val = static_cast<int>(input->readULong(2));
if (val!=0x1b38) {
MWAW_DEBUG_MSG(("MoreText::sendText: field fCol: unexpected end field\n"));
f << "###";
input->seek(-2, librevenge::RVNG_SEEK_CUR);
break;
}
break;
}
case 0x41:
ft.set(MWAWFont::Script(33));
sendFont = false;
f << "@[supersc]";
break;
case 0x42: // in fact, (line bold)^bold
if (defFlags&MWAWFont::boldBit)
ft.setFlags(ft.flags()&uint32_t(~MWAWFont::boldBit));
else
ft.setFlags(ft.flags()|MWAWFont::boldBit);
sendFont = false;
f << "@[b]";
break;
case 0x49: // in fact, (line italic)^italic
if (defFlags&MWAWFont::italicBit)
ft.setFlags(ft.flags()&uint32_t(~MWAWFont::italicBit));
else
ft.setFlags(ft.flags()|MWAWFont::italicBit);
sendFont = false;
f << "@[it]";
break;
case 0x4c:
ft.set(MWAWFont::Script(-33));
sendFont = false;
f << "@[subsc]";
break;
case 0x4f:
if (defFlags&MWAWFont::outlineBit)
ft.setFlags(ft.flags()&uint32_t(~MWAWFont::outlineBit));
else
ft.setFlags(ft.flags()|MWAWFont::outlineBit);
sendFont = false;
f << "@[outline]";
break;
case 0x53:
if (defFlags&MWAWFont::shadowBit)
ft.setFlags(ft.flags()&uint32_t(~MWAWFont::shadowBit));
else
ft.setFlags(ft.flags()|MWAWFont::shadowBit);
sendFont = false;
f << "@[shadow]";
break;
case 0x55:
ft.setUnderlineStyle(defHasUnderline ? MWAWFont::Line::None : MWAWFont::Line::Simple);
sendFont = false;
f << "@[underl]";
break;
case 0x61:
ft.set(MWAWFont::Script());
sendFont = false;
f << "@[script=def]";
break;
case 0x62:
if ((defFlags&MWAWFont::boldBit)==0)
ft.setFlags(ft.flags()&uint32_t(~MWAWFont::boldBit));
else
ft.setFlags(ft.flags()|MWAWFont::boldBit);
sendFont = false;
f << "@[/b]";
break;
case 0x69:
if ((defFlags&MWAWFont::italicBit)==0)
ft.setFlags(ft.flags()&uint32_t(~MWAWFont::italicBit));
else
ft.setFlags(ft.flags()|MWAWFont::italicBit);
sendFont = false;
f << "@[/it]";
break;
case 0x6f:
if ((defFlags&MWAWFont::outlineBit)==0)
ft.setFlags(ft.flags()&uint32_t(~MWAWFont::outlineBit));
else
ft.setFlags(ft.flags()|MWAWFont::outlineBit);
sendFont = false;
f << "@[/outline]";
break;
case 0x73:
if ((defFlags&MWAWFont::shadowBit)==0)
ft.setFlags(ft.flags()&uint32_t(~MWAWFont::shadowBit));
else
ft.setFlags(ft.flags()|MWAWFont::shadowBit);
sendFont = false;
f << "@[/shadow]";
break;
case 0x75:
ft.setUnderlineStyle(defHasUnderline ? MWAWFont::Line::Simple : MWAWFont::Line::None);
sendFont = false;
f << "@[/underl]";
break;
case 0xb9: {
if (actPos+4+8 > endPos) {
f << "@[#field]";
MWAW_DEBUG_MSG(("MoreText::sendText: field b9 seems too short\n"));
break;
}
uint16_t values[4];
for (auto &v : values) v=static_cast<uint16_t>(input->readULong(2));
if (values[0]!=0xc || values[3]!=0xc) {
MWAW_DEBUG_MSG(("MoreText::sendText: field the separator seems bad\n"));
f << "@[field###]";
}
else {
switch (values[1]) {
case 1:
listener->insertUnicodeString(librevenge::RVNGString("#Slide#"));
f << "@[slide/title";
if (values[2]) f << ":" << values[2]; // always 0?
f << "]";
break;
case 3:
listener->insertField(MWAWField(MWAWField::Title));
f << "@[title";
if (values[2]) f << ":" << values[2]; // always 0?
f << "]";
break;
case 4:
listener->insertUnicodeString(librevenge::RVNGString("#Folder#"));
f << "@[folder]";
if (values[2]) f << ":" << values[2]; // always 0?
f << "]";
break;
case 5:
listener->insertField(MWAWField(MWAWField::PageNumber));
f << "@[pNumber";
if (values[2]!=0xa) f << ":" << values[2];
f << "]";
break;
case 6: // actual time
case 7: // last modif
listener->insertField(MWAWField(MWAWField::Time));
f << "@[time";
if (values[1]==7) f << "2";
if (values[2]!=1) f << ":" << values[2];
f << "]";
break;
case 8: // actual date
case 9: // last modif
listener->insertField(MWAWField(MWAWField::Date));
f << "@[date";
if (values[1]==7) f << "2";
if (values[2]!=0x200) f << ":" << values[2];
f << "]";
break;
case 0xc:
listener->insertField(MWAWField(MWAWField::PageCount));
f << "@[pNumber";
if (values[2]!=0xa) f << ":" << values[2];
f << "]";
break;
default:
f << "@[field=##" << values[1] << ":" << values[2] << "]";
MWAW_DEBUG_MSG(("MoreText::sendText: unknown field\n"));
break;
}
}
val = static_cast<int>(input->readULong(2));
if (val!=0x1bb9) {
MWAW_DEBUG_MSG(("MoreText::sendText: field b9: unexpected end field\n"));
f << "###";
input->seek(-2, librevenge::RVNG_SEEK_CUR);
}
break;
}
case 0xf9: {
if (actPos+22 > endPos) {
f << "@[#picture]";
MWAW_DEBUG_MSG(("MoreText::sendText: field f9 seems too short\n"));
break;
}
auto sz=long(input->readULong(4));
if (sz<22 || actPos+sz>=endPos) {
MWAW_DEBUG_MSG(("MoreText::sendText: field f9: bad field size\n"));
f << "###";
input->seek(actPos+2, librevenge::RVNG_SEEK_CUR);
break;
}
input->seek(actPos+sz-6, librevenge::RVNG_SEEK_SET);
if (static_cast<int>(input->readULong(4))!=sz &&
static_cast<int>(input->readULong(2))!=int(0x1b00|fld)) {
MWAW_DEBUG_MSG(("MoreText::sendText: find a unknown picture end field\n"));
f << "@[#" << std::hex << fld << std::dec << ":" << sz << "]";
input->seek(actPos+2, librevenge::RVNG_SEEK_SET);
break;
}
input->seek(actPos+6, librevenge::RVNG_SEEK_SET);
f << "[picture:";
val = static_cast<int>(input->readLong(2));
if (val!=0x100)
f << "type=" << std::hex << val << std::dec << ",";
float dim[4];
for (auto &d : dim) d = float(input->readLong(2));
MWAWBox2f bdbox(MWAWVec2f(dim[1],dim[0]), MWAWVec2f(dim[3],dim[2]));
f << "bdbox=" << bdbox << ",";
if (sz>22) {
std::shared_ptr<MWAWPict> pict(MWAWPictData::get(input, static_cast<int>(sz)-22));
MWAWEmbeddedObject picture;
if (pict && pict->getBinary(picture)) {
MWAWPosition pictPos(MWAWVec2f(0,0), bdbox.size(), librevenge::RVNG_POINT);
pictPos.m_anchorTo = MWAWPosition::Char;
listener->insertPicture(pictPos, picture);
}
#ifdef DEBUG_WITH_FILES
if (1) {
librevenge::RVNGBinaryData file;
input->seek(actPos+16, librevenge::RVNG_SEEK_SET);
input->readDataBlock(sz-22, file);
static int volatile pictName = 0;
libmwaw::DebugStream f2;
f2 << "Pict-" << ++pictName << ".pct";
libmwaw::Debug::dumpFile(file, f2.str().c_str());
ascFile.skipZone(actPos+16, actPos+sz-7);
}
#endif
}
input->seek(actPos+sz, librevenge::RVNG_SEEK_SET);
break;
}
default: {
auto sz=static_cast<int>(input->readULong(2));
if (sz>4 && actPos+sz<=endPos) {
input->seek(actPos+sz-4, librevenge::RVNG_SEEK_SET);
if (static_cast<int>(input->readULong(2))==sz &&
static_cast<int>(input->readULong(2))==int(0x1b00|fld)) {
MWAW_DEBUG_MSG(("MoreText::sendText: find a unknown field, but can infer size\n"));
f << "@[#" << std::hex << fld << std::dec << ":" << sz << "]";
break;
}
input->seek(actPos+2, librevenge::RVNG_SEEK_SET);
}
MWAW_DEBUG_MSG(("MoreText::sendText: find a unknown field\n"));
f << "@[#" << std::hex << fld << std::dec << "]";
break;
}
}
if (!sendFont)
listener->setFont(ft);
}
ascFile.addPos(pos);
ascFile.addNote(f.str().c_str());
ascFile.addPos(entry.end());
ascFile.addNote("_");
return true;
}
//////////////////////////////////////////////
// Fonts
//////////////////////////////////////////////
bool MoreText::readFonts(MWAWEntry const &entry)
{
if (!entry.valid()) {
MWAW_DEBUG_MSG(("MoreText::readFonts: the entry is bad\n"));
return false;
}
long pos = entry.begin();
long endPos = entry.end();
MWAWInputStreamPtr &input= m_parserState->m_input;
libmwaw::DebugFile &ascFile = m_parserState->m_asciiFile;
libmwaw::DebugStream f;
input->seek(pos, librevenge::RVNG_SEEK_SET);
entry.setParsed(true);
int n=0;
while (1) {
pos=input->tell();
if (pos+1 > endPos) {
MWAW_DEBUG_MSG(("MoreText::readFonts: problem reading a font\n"));
break;
}
auto fSz=int(input->readULong(1));
if (fSz==0)
break;
if (pos+1+fSz+2 > endPos) {
input->seek(-1, librevenge::RVNG_SEEK_CUR);
break;
}
f.str("");
if (n==0)
f << "Entries(Fonts)-" << n++ << ",";
else
f << "Fonts-" << n++ << ":";
std::string name("");
for (int i=0; i < fSz; i++)
name+=char(input->readULong(1));
if ((fSz&1)==0) input->seek(1, librevenge::RVNG_SEEK_CUR);
auto id=static_cast<int>(input->readULong(2));
f << name << ",id=" << id << ",";
if (name.length())
m_parserState->m_fontConverter->setCorrespondance(id, name);
ascFile.addPos(pos);
ascFile.addNote(f.str().c_str());
}
pos = input->tell();
if (pos != endPos) {
MWAW_DEBUG_MSG(("MoreText::readFonts: problem reading a font\n"));
ascFile.addPos(pos);
ascFile.addNote("Fonts:###");
}
return true;
}
//////////////////////////////////////////////
// outline
//////////////////////////////////////////////
bool MoreText::readOutlineList(MWAWEntry const &entry)
{
if (!entry.valid() || (entry.length()%4)) {
MWAW_DEBUG_MSG(("MoreText::readOutlineList: the entry is bad\n"));
return false;
}
long pos = entry.begin();
MWAWInputStreamPtr &input= m_parserState->m_input;
libmwaw::DebugFile &ascFile = m_parserState->m_asciiFile;
libmwaw::DebugStream f;
input->seek(pos, librevenge::RVNG_SEEK_SET);
entry.setParsed(true);
f << "Entries(Outline):";
auto N=int(entry.length()/4);
std::vector<MWAWEntry> posList;
for (int i=0; i < N; i++) {
MWAWEntry tEntry;
tEntry.setBegin(input->readLong(4));
tEntry.setId(int(i));
if (!m_mainParser->checkAndFindSize(tEntry)) {
MWAW_DEBUG_MSG(("MoreText::readOutlineList: can not read a file position\n"));
f << "###,";
}
else
f << std::hex << tEntry.begin() << "<->" << tEntry.end() << ",";
posList.push_back(tEntry);
}
ascFile.addPos(pos);
ascFile.addNote(f.str().c_str());
for (auto const &tEntry : posList) {
if (!tEntry.valid())
continue;
MoreTextInternal::Outline outline;
if (readOutline(tEntry, outline)) {
m_state->m_outlineList.push_back(outline);
continue;
}
m_state->m_outlineList.push_back(MoreTextInternal::Outline());
ascFile.addPos(tEntry.begin());
ascFile.addNote("Outline-data:###");
ascFile.addPos(tEntry.end());
ascFile.addNote("_");
}
return true;
}
bool MoreText::readOutline(MWAWEntry const &entry, MoreTextInternal::Outline &outline)
{
if (!entry.valid() || entry.length()<8) {
MWAW_DEBUG_MSG(("MoreText::readOutline: the entry is bad\n"));
return false;
}
int vers = version();
long pos = entry.begin();
long endPos = entry.end();
MWAWInputStreamPtr &input= m_parserState->m_input;
libmwaw::DebugFile &ascFile = m_parserState->m_asciiFile;
libmwaw::DebugStream f;
input->seek(pos+4, librevenge::RVNG_SEEK_SET); // skip size
f << "Outline[O" << entry.id() << "]:";
auto val=static_cast<int>(input->readULong(2));
if (val!=6*(vers-1)) {
MWAW_DEBUG_MSG(("MoreText::readOutline: find unexpected type\n"));
f << "#f0=" << val << ",";
}
auto N=static_cast<int>(input->readULong(2));
f << "N=" << N << ",";
long lastListPos = pos+8+N*16;
if (lastListPos > endPos) {
MWAW_DEBUG_MSG(("MoreText::readOutline: can not read length\n"));
return false;
}
ascFile.addPos(pos);
ascFile.addNote(f.str().c_str());
std::vector<MoreTextInternal::OutlineMod> outlineModList;
for (int n=0; n<N; n++) {
pos = input->tell();
f.str("");
MoreTextInternal::OutlineMod outlineMod;
val=int(input->readLong(1));
if (val!=6*(vers-1))
f << "#f0=" << val << ",";
outlineMod.m_flags=int(input->readULong(1));
for (auto &unkn : outlineMod.m_unknowns) unkn=int(input->readLong(2));
outlineMod.m_type=int(input->readULong(2));
int values[4];
for (auto &v : values) v = static_cast<int>(input->readULong(2));
int const which= outlineMod.getModId();
auto ¶ = outline.m_paragraphs[which];
MWAWFont &font=outline.m_fonts[which];
uint32_t fFlags=font.flags();
bool haveExtra=false;
switch (outlineMod.m_type) {
case 0x301: // font name
case 0xf07: // left indent+tabs
haveExtra=true;
break;
case 0x402:
// size can be very big, force it to be smallest than 100
if (values[0]>0 && values[0] <= 100) {
font.setSize(float(values[0]));
f << "sz=" << values[0] << ",";
}
else {
MWAW_DEBUG_MSG(("MoreText::readOutline: the font size seems bad\n"));
f << "##sz=" << values[0] << ",";
}
break;
case 0x603: {
uint32_t bit=0;
switch (values[0]) {
case 0:
f << "plain";
if (values[1]==1)
fFlags=0;
break;
case 1:
bit = MWAWFont::boldBit;
f << "b";
break;
case 2:
bit = MWAWFont::italicBit;
f << "it";
break;
case 3:
if (values[1]==1)
font.setUnderlineStyle(MWAWFont::Line::Simple);
f << "underl";
break;
case 4:
bit = MWAWFont::outlineBit;
f << "outline";
break;
case 5:
bit = MWAWFont::shadowBit;
f << "shadow";
break;
default:
f << "##fl=" << std::hex << values[0] << std::dec;
break;
}
if (values[1]==1) {
if (bit) fFlags = fFlags & (~bit);
f << "[of],";
}
else if (values[1]!=0)
f << "=##" << values[1] << ",";
else
fFlags |= bit;
values[1]=0;
break;
}
case 0x804: {
MWAWColor col(static_cast<unsigned char>(static_cast<uint16_t>(values[0])>>8),
static_cast<unsigned char>(static_cast<uint16_t>(values[1])>>8),
static_cast<unsigned char>(static_cast<uint16_t>(values[2])>>8));
font.setColor(col);
f << col << ",";
values[1]=values[2]=0;
break;
}
case 0xa05:
if (values[0]&0x8000) {
para.setInterline(double(values[0]&0x7FFF)/20., librevenge::RVNG_POINT, MWAWParagraph::AtLeast);
f << "interline=" << *para.m_spacings[0] << "pt,";
}
else {
para.setInterline(double(values[0])/double(0x1000), librevenge::RVNG_PERCENT);
f << "interline=" << 100* *para.m_spacings[0] << "%,";
}
break;
case 0xc0f: // firstIndent
para.m_margins[0] = double(values[0])/1440.;
f << "indent=" << *para.m_margins[0] << ",";
break;
case 0x1006:
switch (values[0]) {
case 0:
para.m_justify = MWAWParagraph::JustificationLeft;
f << "left,";
break;
case 1:
para.m_justify = MWAWParagraph::JustificationCenter;
f << "center,";
break;
case 2:
para.m_justify = MWAWParagraph::JustificationRight;
f << "right,";
break;
case 3:
para.m_justify = MWAWParagraph::JustificationFull;
f << "full,";
break;
default:
f << "##justify=" << values[0] << ",";
break;
}
break;
case 0x1208:
if (values[0] & 0x8000) {
para.m_spacings[1]=double(values[0]&0x7FFF)/1440.;
f << "bef=" << double(values[0]&0x7FFF)/20. << "pt,";
}
else {
// assume 12pt
para.m_spacings[1]=double(values[0])/double(0x1000)*12./72.;
if (values[0])
f << "bef=" << 100.*double(values[0])/double(0x1000) << "%,";
}
if (values[1] & 0x8000) {
para.m_spacings[2]=double(values[1]&0x7FFF)/1440.;
f << "aft=" << double(values[1]&0x7FFF)/20. << "pt,";
}
else {
para.m_spacings[2]=double(values[1])/double(0x1000)*12./72.;
if (values[1])
f << "aft=" << 100.*double(values[1])/double(0x1000) << "%,";
}
values[1]=0;
break;
case 0x1409: // lMargin in TWIP
para.setLeftMargin(double(values[0]&0x7FFF)/1440., (values[0]&0x8000)==0);
if (values[0]&0x8000)
f << "indent=" << double(values[0]&0x7FFF)/1440. << ",";
else // checkme
f << "indent=" << double(values[0])/1440. << "[fromParent],";
break;
case 0x160a: // rMargin in TWIP
para.setRightMargin(double(values[0]&0x7FFF)/1440., (values[0]&0x8000)==0);
if (values[0]&0x8000)
f << "indent=" << double(values[0]&0x7FFF)/1440. << ",";
else // checkme
f << "indent=" << double(values[0])/1440. << "[fromParent],";
break;
case 0x1a0c:
para.m_pageBreak=(values[0]==0x100);
if (values[0]==0x100)
f << "pagebreak,";
else if (values[0]==0)
f << "no,";
else
f << "##break=" << std::hex << values[0] << std::dec << ",";
break;
case 0x1c0d:
if (values[0]==0x100) {
para.m_breakStatus = (*para.m_breakStatus)|MWAWParagraph::NoBreakWithNextBit;
f << "together,";
}
else if (values[0]==0) {
para.m_breakStatus = (*para.m_breakStatus)|int(~MWAWParagraph::NoBreakWithNextBit);
f << "no,";
}
else
f << "#keepLine=" << std::hex << values[0] << std::dec << ",";
break;
case 0x1e0e:
para.m_keepOutlineTogether = (values[0]==0x100);
if (values[0]==0x100)
f << "together,";
else if (values[0]==0)
f << "no,";
else
f << "#keepOutline=" << std::hex << values[0] << std::dec << ",";
break;
case 0x190b:
para.m_listType = values[0];
switch (values[0]) {
case 0:
f << "no,";
break;
case 1:
f << "leader,";
break;
case 2:
f << "hardvard,";
break;
case 3:
f << "numeric,";
break;
case 4:
f << "legal,";
break;
case 5:
f << "bullets,";
break;
default:
if (values[0]>=11) {
f << "custom[" << values[0] << "],";
haveExtra=true;
break;
}
f << "##bullet=" << values[0] << ",";
break;
}
break;
default:
if (values[0])
f << "f2=" << std::hex << values[0] << std::dec << ",";
// use heuristic to define extra data
if (values[0]>0x2800)
haveExtra = (values[0] & 0x0100);
else
haveExtra=values[1]==0;
break;
}
font.setFlags(fFlags);
if (values[1]) f << "g0=" << std::hex << values[1] << std::dec << ",";
if (haveExtra && values[3]>0 &&
lastListPos+values[2]+values[3] <= endPos) {
outlineMod.m_entry.setBegin(lastListPos+values[2]);
outlineMod.m_entry.setLength(values[3]);
outlineMod.m_entry.setId(n);
}
else {
for (int i=2; i < 4; i++) {
if (values[i])
f << "g" << i-1 << "=" << std::hex << values[i] << std::dec << ",";
}
}
outlineMod.m_extra=f.str();
f.str("");
f << "Outline[O" << entry.id() << "-" << n << "]:" << outlineMod;
outlineModList.push_back(outlineMod);
ascFile.addPos(pos);
ascFile.addNote(f.str().c_str());
input->seek(pos+16, librevenge::RVNG_SEEK_SET);
}
int n=0;
for (auto const &outlineMod : outlineModList) {
n++;
if (!outlineMod.m_entry.valid())
continue;
f.str("");
f << "Outline[O" << entry.id() << "-A" << n-1 << "]:";
bool ok=false;
auto ¶ = outline.m_paragraphs[outlineMod.getModId()];
MWAWFont &font=outline.m_fonts[outlineMod.getModId()];
switch (outlineMod.m_type) {
case 0x301: {
std::string fName;
int fId;
ok = readFont(outlineMod.m_entry, fName,fId);
if (!ok) break;
f << "font=[";
f << "name=" << fName;
if (fId>=0) {
f << ":" << fId;
font.setId(fId);
}
f << "],";
break;
}
case 0xf07: {
std::string mess;
ok = readTabs(outlineMod.m_entry, para, mess);
if (!ok) break;
f << "tabs=[" << mess << "],";
break;
}
case 0x190b: {
ok = readCustomListLevel(outlineMod.m_entry, para.m_customListLevel);
if (!ok) break;
f << para.m_customListLevel << ",";
break;
}
default:
break;
}
// can be also pattern or backside or custom header
if (!ok) {
f << "[" << outlineMod << "]";
if (!parseUnknown(outlineMod.m_entry, 0))
f << "###";
}
ascFile.addPos(outlineMod.m_entry.begin());
ascFile.addNote(f.str().c_str());
}
ascFile.addPos(endPos);
ascFile.addNote("_");
return true;
}
//////////////////////////////////////////////
// small structure
//////////////////////////////////////////////
bool MoreText::readFont(MWAWEntry const &entry, std::string &fName, int &fId)
{
fName="";
fId=-1;
if (entry.length() < 2)
return false;
MWAWInputStreamPtr &input= m_parserState->m_input;
long pos = entry.begin();
input->seek(pos, librevenge::RVNG_SEEK_SET);
auto fSz=static_cast<int>(input->readULong(1));
long remain=entry.length()-long(1+fSz);
if (fSz==0 || remain<0 || remain==1)
return false;
if (remain>=2 && remain!=2+(1-(fSz%2)))
return false;
for (int i=0; i < fSz; i++) {
auto c=char(input->readULong(1));
if (c==0) return false;
fName+=c;
}
if (remain==0) { // let try to retrieve the font id
fId=m_parserState->m_fontConverter->getId(fName);
return true;
}
if ((fSz%2)==0) input->seek(1,librevenge::RVNG_SEEK_CUR);
fId=static_cast<int>(input->readULong(2));
return true;
}
bool MoreText::readCustomListLevel(MWAWEntry const &entry, MWAWListLevel &level)
{
level=MWAWListLevel();
if (entry.length()<22)
return false;
MWAWInputStreamPtr &input= m_parserState->m_input;
libmwaw::DebugStream f;
long pos = entry.begin();
input->seek(pos, librevenge::RVNG_SEEK_SET);
MWAWFont font;
auto fId=static_cast<int>(input->readULong(2));
if (fId==0xFFFF) // default
;
else if (fId&0x8000) {
f << "fId=" << (fId&0x7FFF) << ",";
font.setId(fId&0x7FFF);
}
else
f << "#fId=" << std::hex << fId << std::dec << ",";
auto fSz = static_cast<int>(input->readLong(2));
if (fSz != -1) {
font.setSize(float(fSz));
f << "fSz=" << fSz << ",";
}
auto fFlags=static_cast<int>(input->readULong(1));
uint32_t flags=0;
if (fFlags&1) flags |= MWAWFont::boldBit;
if (fFlags&2) flags |= MWAWFont::italicBit;
if (fFlags&4) font.setUnderlineStyle(MWAWFont::Line::Simple);
if (fFlags&8) flags |= MWAWFont::outlineBit;
if (fFlags&0x10) flags |= MWAWFont::shadowBit;
if (fFlags&0xE0)
f << "#fFlags=" << std::hex << (fFlags&0xE0) << std::dec << ",";
font.setFlags(flags);
auto fColor = static_cast<int>(input->readLong(1));
if (fColor==1)
input->seek(6, librevenge::RVNG_SEEK_CUR);
else if (fColor==3) {
unsigned char color[3];
for (auto &c : color) c=static_cast<unsigned char>(input->readULong(2)>>8);
font.setColor(MWAWColor(color[0], color[1], color[2]));
}
else {
f << "#fCol=" << fColor << ",";
input->seek(6, librevenge::RVNG_SEEK_CUR);
}
#if defined(DEBUG_WITH_FILES)
f << "font=[" << font.getDebugString(m_parserState->m_fontConverter) << "],";
#endif
// now 4 bool
bool bVal[4]= {false, false, false, false };
long val;
for (int i=0; i < 4; i++) {
val = input->readLong(1);
if (!val) continue;
if (val!=1) {
f << "#g" << i << "=" << val << ",";
continue;
}
bVal[i]=true;
}
for (int i=0; i<2; i++) { // g1: field left ident modify?
if (bVal[i])
f << "g" << i << "=true,";
}
if (bVal[2]) // or flush left at
f << "flushRight,";
if (!bVal[3])
f << "useFirstlineIdent,";
val = input->readLong(2);
if (val!=0x2d0) // default 0.5"
f << "leftIdent=" << double(val)/1440. << ",";
val = input->readLong(2);
if (val != 0xb)
f << "f6=" << val << ",";
if (fId!=0xFFFF) { // maybe the font name
auto fontSz=static_cast<int>(input->readULong(1));
if (!fontSz || input->tell()+fontSz >= entry.end())
input->seek(-1,librevenge::RVNG_SEEK_CUR);
else {
std::string fName("");
for (int i=0; i<fontSz; i++)
fName+=char(input->readULong(1));
f << "fName=" << fName << ",";
int newId=m_parserState->m_fontConverter->getId(fName);
if (newId > 0)
font.setId(fId=newId);
}
}
auto labelSz = static_cast<int>(input->readULong(1));
if (input->tell()+labelSz != entry.end())
return false;
f << "label=";
if (fId == 0xFFFF)
fId=3;
for (int c=0; c < labelSz; c++) {
auto ch=static_cast<unsigned char>(input->readULong(1));
f << ch;
int unicode = m_parserState->m_fontConverter->unicode(fId, static_cast<unsigned char>(ch));
if (unicode!=-1)
libmwaw::appendUnicode(uint32_t(unicode), level.m_label);
else if (ch==0x9 || ch > 0x1f)
libmwaw::appendUnicode(static_cast<uint32_t>(c), level.m_label);
else {
f << "##";
MWAW_DEBUG_MSG(("MoreText::readCustomListLevel: label char seems bad\n"));
libmwaw::appendUnicode('#', level.m_label);
}
}
f << ",";
level.m_type=MWAWListLevel::LABEL;
level.m_extra=f.str();
if (input->tell()!=entry.end())
m_parserState->m_asciiFile.addDelimiter(input->tell(),'|');
return true;
}
bool MoreText::readTabs(MWAWEntry const &entry, MoreTextInternal::Paragraph ¶,
std::string &mess)
{
mess="";
if (entry.length() < 4)
return false;
MWAWInputStreamPtr &input= m_parserState->m_input;
libmwaw::DebugStream f;
long pos = entry.begin();
input->seek(pos, librevenge::RVNG_SEEK_SET);
auto nTabs=static_cast<int>(input->readULong(2));
if (entry.length()!=4+4*nTabs)
return false;
auto repeat=static_cast<int>(input->readLong(2));
if (uint16_t(repeat)==0x8000) // special case
f << "def[center,right],";
else
f << "repeat=" << double(repeat)/1440. << ",";
para.m_tabs->resize(0);
for (int i=0; i < nTabs; i++) {
libmwaw::DebugStream f2;
MWAWTabStop tab;
tab.m_position = double(input->readULong(2))/1440.;
auto val=static_cast<int>(input->readULong(1));
switch (val&0xF) {
case 1: // left
break;
case 2:
tab.m_alignment=MWAWTabStop::CENTER;
break;
case 3:
tab.m_alignment=MWAWTabStop::RIGHT;
break;
case 4:
tab.m_alignment=MWAWTabStop::DECIMAL;
break;
default:
f2 << "#align=" << (val&0xF) << ",";
break;
}
switch (val>>4) {
case 0: // none
break;
case 1:
tab.m_leaderCharacter = '_';
break;
case 3: // more large space
f2 << "dot[large],";
MWAW_FALLTHROUGH;
case 2:
tab.m_leaderCharacter = '.';
break;
default:
f2 << "#leader=" << (val>>4) << ",";
break;
}
auto decimalChar = char(input->readULong(1));
if (decimalChar) {
int unicode= m_parserState->m_fontConverter->unicode(3, static_cast<unsigned char>(decimalChar));
if (unicode==-1)
tab.m_decimalCharacter = uint16_t(decimalChar);
else
tab.m_decimalCharacter = uint16_t(unicode);
}
f << "tab" << i << "=[" << tab << "," << f2.str() << "],";
para.m_tabs->push_back(tab);
}
mess=f.str();
return true;
}
//////////////////////////////////////////////
// unknown structure
//////////////////////////////////////////////
bool MoreText::parseUnknown(MWAWEntry const &entry, long fDecal)
{
MWAWInputStreamPtr &input= m_parserState->m_input;
libmwaw::DebugFile &ascFile = m_parserState->m_asciiFile;
libmwaw::DebugStream f;
MoreStruct::Pattern pattern;
long pos = entry.begin();
input->seek(pos, librevenge::RVNG_SEEK_SET);
if (m_mainParser->readPattern(entry.end(),pattern)) {
f << pattern;
if (input->tell()!=entry.end())
ascFile.addDelimiter(input->tell(),'|');
ascFile.addPos(pos+fDecal);
ascFile.addNote(f.str().c_str());
return true;
}
// can we find a backsidde here
input->seek(pos, librevenge::RVNG_SEEK_SET);
std::string extra("");
if (m_mainParser->readBackside(entry.end(), extra)) {
f << extra;
if (input->tell()!=entry.end())
ascFile.addDelimiter(input->tell(),'|');
ascFile.addPos(pos+fDecal);
ascFile.addNote(f.str().c_str());
return true;
}
std::string mess;
MoreTextInternal::Paragraph para;
if (readTabs(entry, para, mess)) {
f << "tabs=[" << mess << "],";
ascFile.addPos(pos+fDecal);
ascFile.addNote(f.str().c_str());
return true;
}
std::string fName;
int fId;
if (readFont(entry, fName,fId)) {
f << "font=[";
f << "name=" << fName;
if (fId>=0) f << ":" << fId;
f << "],";
ascFile.addPos(pos+fDecal);
ascFile.addNote(f.str().c_str());
return true;
}
return false;
}
// vim: set filetype=cpp tabstop=2 shiftwidth=2 cindent autoindent smartindent noexpandtab: