mirror of
https://github.com/fadden/ciderpress.git
synced 2024-11-26 02:49:20 +00:00
bb24f51ccb
We weren't doing a MOR-to-UNI conversion on the sub-volume name, so HFS volumes with non-ASCII characters didn't look right. This also relocates the character-conversion code to a new source file. It's currently part of the reformat lib, though it arguably belongs in util (but that would introduce a new dependency between reformat and util).
412 lines
12 KiB
C++
412 lines
12 KiB
C++
/*
|
|
* CiderPress
|
|
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
|
|
* See the file LICENSE for distribution terms.
|
|
*/
|
|
/*
|
|
* Reformat "generic" GWP and Teach files.
|
|
*/
|
|
#include "StdAfx.h"
|
|
#include "Teach.h"
|
|
#include "ResourceFork.h"
|
|
|
|
|
|
/*
|
|
* ==========================================================================
|
|
* ReformatGWP
|
|
* ==========================================================================
|
|
*/
|
|
|
|
/*
|
|
* Decide whether or not we want to treat this file as a "generic" IIgs
|
|
* document. Possibly useful for converting the special characters. We'll
|
|
* activate it for any GWP file other than the two known types.
|
|
*/
|
|
void ReformatGWP::Examine(ReformatHolder* pHolder)
|
|
{
|
|
ReformatHolder::ReformatApplies applies = ReformatHolder::kApplicNot;
|
|
|
|
if (pHolder->GetFileType() == kTypeGWP &&
|
|
(pHolder->GetAuxType() != 0x5445 && pHolder->GetAuxType() != 0x8010))
|
|
{
|
|
applies = ReformatHolder::kApplicProbably;
|
|
}
|
|
|
|
pHolder->SetApplic(ReformatHolder::kReformatGWP, applies,
|
|
ReformatHolder::kApplicNot, ReformatHolder::kApplicNot);
|
|
}
|
|
|
|
/*
|
|
* Convert GWP into formatted text.
|
|
*/
|
|
int ReformatGWP::Process(const ReformatHolder* pHolder,
|
|
ReformatHolder::ReformatID id, ReformatHolder::ReformatPart part,
|
|
ReformatOutput* pOutput)
|
|
{
|
|
const uint8_t* srcBuf = pHolder->GetSourceBuf(part);
|
|
long srcLen = pHolder->GetSourceLen(part);
|
|
fUseRTF = false;
|
|
|
|
Charset::CheckGSCharConv();
|
|
RTFBegin();
|
|
|
|
/* convert EOL markers and IIgs characters */
|
|
uint8_t ch;
|
|
while (srcLen) {
|
|
ch = *srcBuf++;
|
|
srcLen--;
|
|
|
|
if (ch == '\r') {
|
|
/* got CR, check for CRLF -- not really expected on IIgs */
|
|
if (srcLen != 0 && *srcBuf == '\n') {
|
|
srcBuf++;
|
|
srcLen--;
|
|
}
|
|
BufPrintf("\r\n");
|
|
} else if (ch == '\n') {
|
|
BufPrintf("\r\n");
|
|
} else {
|
|
// RTF is always off, so just use BufPrintf
|
|
BufPrintf("%c", Charset::ConvertMacRomanTo1252(ch));
|
|
}
|
|
}
|
|
|
|
RTFEnd();
|
|
|
|
SetResultBuffer(pOutput);
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* ==========================================================================
|
|
* ReformatTeach
|
|
* ==========================================================================
|
|
*/
|
|
|
|
/*
|
|
* Decide whether or not we want to handle this file.
|
|
*/
|
|
void ReformatTeach::Examine(ReformatHolder* pHolder)
|
|
{
|
|
ReformatHolder::ReformatApplies applies = ReformatHolder::kApplicNot;
|
|
|
|
if (pHolder->GetFileType() == kTypeGWP && pHolder->GetAuxType() == 0x5445)
|
|
applies = ReformatHolder::kApplicYes;
|
|
|
|
pHolder->SetApplic(ReformatHolder::kReformatTeach, applies,
|
|
ReformatHolder::kApplicNot, ReformatHolder::kApplicNot);
|
|
}
|
|
|
|
/*
|
|
* Convert Teach Text into formatted text.
|
|
*
|
|
* The text is in the data fork and the formatting is in the resource fork.
|
|
*/
|
|
int ReformatTeach::Process(const ReformatHolder* pHolder,
|
|
ReformatHolder::ReformatID id, ReformatHolder::ReformatPart part,
|
|
ReformatOutput* pOutput)
|
|
{
|
|
const uint8_t* dataBuf;
|
|
const uint8_t* rsrcBuf;
|
|
long dataLen, rsrcLen;
|
|
const uint8_t* styleBlock;
|
|
long styleLen;
|
|
|
|
if (part != ReformatHolder::kPartData)
|
|
return -1;
|
|
|
|
dataBuf = pHolder->GetSourceBuf(ReformatHolder::kPartData);
|
|
dataLen = pHolder->GetSourceLen(ReformatHolder::kPartData);
|
|
rsrcBuf = pHolder->GetSourceBuf(ReformatHolder::kPartRsrc);
|
|
rsrcLen = pHolder->GetSourceLen(ReformatHolder::kPartRsrc);
|
|
if (dataBuf == NULL || rsrcBuf == NULL || dataLen <= 0 || rsrcLen <= 0) {
|
|
LOGI("Teach reformatter missing one fork of the file");
|
|
return -1;
|
|
}
|
|
Charset::CheckGSCharConv();
|
|
|
|
/* find the rStyleBlock */
|
|
if (!ReformatResourceFork::GetResource(rsrcBuf, rsrcLen, 0x8012, 0x0001,
|
|
&styleBlock, &styleLen))
|
|
{
|
|
LOGI("Resource fork of Teach Text file not found");
|
|
return -1;
|
|
}
|
|
|
|
RStyleBlock rStyleBlock;
|
|
if (!rStyleBlock.Create(styleBlock, styleLen)) {
|
|
LOGI("Unable to unpack rStyleBlock");
|
|
return -1;
|
|
}
|
|
|
|
fUseRTF = true;
|
|
RTFBegin();
|
|
|
|
/*
|
|
* Configure ruler options. The format allows us to have more than one
|
|
* ruler, but doesn't provide a way to select between them, so we have
|
|
* to assume that the first ruler is global.
|
|
*
|
|
* In practice, the Teach application doesn't provide a way to set
|
|
* margins or tab stops, so there's really not much to do. It seems to
|
|
* create tab stops with 64-pixel widths, which translates to 10 tab
|
|
* stops per page. WordPad seems to default to 12, so I figure there's
|
|
* not much point in futzing with it since the fonts are all different
|
|
* anyway.
|
|
*/
|
|
RStyleBlock::TERuler* pRuler = rStyleBlock.GetRuler(0);
|
|
assert(pRuler != NULL);
|
|
if (pRuler->GetJustification() != RStyleBlock::TERuler::kJustLeft) {
|
|
LOGI("WARNING: not using left justified Teach text");
|
|
/* ignore it */
|
|
}
|
|
|
|
for (int style = 0; style < rStyleBlock.GetNumStyleItems(); style++) {
|
|
RStyleBlock::StyleItem* pStyleItem;
|
|
RStyleBlock::TEStyle* pStyle;
|
|
int numBytes;
|
|
|
|
/* set up the style */
|
|
pStyleItem = rStyleBlock.GetStyleItem(style);
|
|
assert(pStyleItem != NULL);
|
|
if (pStyleItem->GetLength() == RStyleBlock::StyleItem::kUnusedItem)
|
|
continue;
|
|
numBytes = pStyleItem->GetLength();
|
|
pStyle = rStyleBlock.GetStyle(pStyleItem->GetStyleIndex());
|
|
|
|
/*
|
|
* Set the font first; that defines the point size multiplier. Set
|
|
* the size second, because the point size determines whether we
|
|
* show underline. Set the style last.
|
|
*/
|
|
RTFSetGSFont(pStyle->GetFontFamily());
|
|
RTFSetGSFontSize(pStyle->GetFontSize());
|
|
RTFSetGSFontStyle(pStyle->GetTextStyle());
|
|
|
|
/*
|
|
* The Teach app doesn't let you set text colors, but it appears
|
|
* there's a way to do it, since the SpyHunterGS Read.Me file has
|
|
* a bit of red in it.
|
|
*/
|
|
if (pStyle->GetTextColor() != 0) {
|
|
// do something with color, someday
|
|
}
|
|
|
|
/* output the characters */
|
|
uint8_t uch;
|
|
while (numBytes--) {
|
|
if (!dataLen) {
|
|
LOGI("WARNING: Teach underrun (%ld wanted)", numBytes);
|
|
break;
|
|
}
|
|
uch = *dataBuf;
|
|
if (uch == '\r') {
|
|
RTFNewPara();
|
|
} else if (uch == '\t') {
|
|
RTFTab();
|
|
} else {
|
|
RTFPrintUTF16Char(Charset::ConvertMacRomanToUTF16(uch));
|
|
}
|
|
dataBuf++;
|
|
dataLen--;
|
|
}
|
|
}
|
|
if (dataLen) {
|
|
LOGI("WARNING: Teach overrun (%ld remain)", dataLen);
|
|
/* no big deal */
|
|
}
|
|
|
|
|
|
RTFEnd();
|
|
|
|
SetResultBuffer(pOutput, true);
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* ==========================================================================
|
|
* RStyleBlock functions
|
|
* ==========================================================================
|
|
*/
|
|
|
|
/*
|
|
* Unpack an rStyleBlock resource.
|
|
*/
|
|
bool RStyleBlock::Create(const uint8_t* buf, long len)
|
|
{
|
|
uint16_t version;
|
|
uint32_t partLen;
|
|
int i;
|
|
|
|
assert(buf != NULL);
|
|
if (len < kMinLen) {
|
|
LOGI("Too short to be rStyleBlock (%d)", len);
|
|
return false;
|
|
}
|
|
|
|
version = Reformat::Read16(&buf, &len);
|
|
if (version != kExpectedVersion) {
|
|
LOGI("Bad rStyleBlock version (%d)", version);
|
|
return false;
|
|
}
|
|
|
|
/* extract ruler(s) */
|
|
partLen = Reformat::Read32(&buf, &len);
|
|
if (partLen > (uint32_t) (len+8)) {
|
|
/* not enough to satisfy data + two more counts */
|
|
LOGI("Invalid part1 length (%d vs %d)", partLen, len);
|
|
return false;
|
|
}
|
|
|
|
fNumRulers = 1;
|
|
fpRulers = new TERuler[fNumRulers];
|
|
if (fpRulers == NULL)
|
|
return false;
|
|
if (fpRulers->Create(buf, partLen) <= 0)
|
|
return false;
|
|
|
|
buf += partLen;
|
|
len -= partLen;
|
|
|
|
/* extract TEStyles */
|
|
partLen = Reformat::Read32(&buf, &len);
|
|
if (partLen > (uint32_t) (len + 4)) {
|
|
LOGI("Invalid part2 length (%d vs %d)", partLen, len);
|
|
return false;
|
|
}
|
|
if ((partLen % TEStyle::kDataLen) != 0) {
|
|
LOGI("Invalid part2 length (%d mod %d)",
|
|
partLen, TEStyle::kDataLen);
|
|
return false;
|
|
}
|
|
|
|
fNumStyles = partLen / TEStyle::kDataLen;
|
|
fpStyles = new TEStyle[fNumStyles];
|
|
if (fpStyles == NULL)
|
|
return false;
|
|
for (i = 0; i < fNumStyles; i++) {
|
|
fpStyles[i].Create(buf);
|
|
buf += TEStyle::kDataLen;
|
|
len -= TEStyle::kDataLen;
|
|
}
|
|
|
|
/* extract StyleItems */
|
|
fNumStyleItems = (int) Reformat::Read32(&buf, &len);
|
|
partLen = fNumStyleItems * StyleItem::kItemDataLen;
|
|
if (partLen > (uint32_t) len) {
|
|
LOGI("Invalid part3 length (%d vs %d)", partLen, len);
|
|
return false;
|
|
}
|
|
|
|
fpStyleItems = new StyleItem[fNumStyleItems];
|
|
if (fpStyleItems == NULL)
|
|
return false;
|
|
for (i = 0; i < fNumStyleItems; i++) {
|
|
fpStyleItems[i].Create(buf);
|
|
if ((fpStyleItems[i].GetOffset() % TEStyle::kDataLen) != 0) {
|
|
LOGI("Invalid offset %d (mod %d)",
|
|
fpStyleItems[i].GetOffset(), TEStyle::kDataLen);
|
|
return false;
|
|
}
|
|
buf += StyleItem::kItemDataLen;
|
|
len -= StyleItem::kItemDataLen;
|
|
}
|
|
|
|
if (len != 0) {
|
|
LOGI("WARNING: at end of rStyleBlock, len is %ld", len);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Construct a TERuler from a chunk of data.
|
|
*
|
|
* Returns the #of bytes consumed, or -1 on failure.
|
|
*/
|
|
int RStyleBlock::TERuler::Create(const uint8_t* buf, long len)
|
|
{
|
|
long origLen = len;
|
|
|
|
if (len < kMinLen)
|
|
return -1;
|
|
|
|
fLeftMargin = Reformat::Get16LE(buf);
|
|
fLeftIndent = Reformat::Get16LE(buf + 2);
|
|
fRightMargin = Reformat::Get16LE(buf + 4);
|
|
fJust = Reformat::Get16LE(buf + 6);
|
|
fExtraLS = Reformat::Get16LE(buf + 8);
|
|
fFlags = Reformat::Get16LE(buf + 10);
|
|
fUserData = Reformat::Get32LE(buf + 12);
|
|
fTabType = Reformat::Get16LE(buf + 16);
|
|
buf += 18;
|
|
len -= 18;
|
|
|
|
/*
|
|
* What we do now depends on the value of fTabType:
|
|
* 0: no tabs are set, there's nothing else to read.
|
|
* 1: regularly-spaced tabs; spacing defined by fTabTerminator field.
|
|
* 2: irregular tabs, defined by array terminated by 0xffff in the
|
|
* fTabTerminator field.
|
|
*
|
|
* At present we're just throwing the tab array out.
|
|
*/
|
|
switch (fTabType) {
|
|
case 0:
|
|
fTabTerminator = 0;
|
|
break;
|
|
case 1:
|
|
fTabTerminator = Reformat::Get16LE(buf);
|
|
buf += 2;
|
|
len -= 2;
|
|
break;
|
|
case 2:
|
|
while (len >= 2) {
|
|
TabItem tabItem;
|
|
tabItem.tabKind = Reformat::Get16LE(buf);
|
|
buf += 2;
|
|
len -= 2;
|
|
|
|
if (tabItem.tabKind == kTabArrayEnd || len < 2)
|
|
break;
|
|
tabItem.tabData = Reformat::Get16LE(buf);
|
|
buf += 2;
|
|
len -= 2;
|
|
}
|
|
break;
|
|
default:
|
|
LOGI("Invalid tab type %d", fTabType);
|
|
return -1;
|
|
}
|
|
|
|
LOGI("TERuler consumed %ld bytes (%ld left over)", origLen - len, len);
|
|
return origLen - len;
|
|
}
|
|
|
|
/*
|
|
* Extract a TEStyle object from the buffer.
|
|
*/
|
|
void RStyleBlock::TEStyle::Create(const uint8_t* buf)
|
|
{
|
|
fFontID = Reformat::Get32LE(buf);
|
|
fForeColor = Reformat::Get16LE(buf + 4);
|
|
fBackColor = Reformat::Get16LE(buf + 6);
|
|
fUserData = Reformat::Get32LE(buf + 8);
|
|
|
|
LOGI(" TEStyle: font fam=0x%04x size=%-2d style=0x%02x fore=0x%04x",
|
|
GetFontFamily(), GetFontSize(), GetTextStyle(), fForeColor);
|
|
}
|
|
|
|
/*
|
|
* Extract a StyleItem object from the buffer.
|
|
*/
|
|
void RStyleBlock::StyleItem::Create(const uint8_t* buf)
|
|
{
|
|
fLength = Reformat::Get32LE(buf);
|
|
fOffset = Reformat::Get32LE(buf + 4);
|
|
|
|
LOGI(" StyleItem: len=%ld off=%ld", fLength, fOffset);
|
|
}
|