ciderpress/reformat/Reformat.cpp

583 lines
20 KiB
C++

/*
* CiderPress
* Copyright (C) 2007, 2008 by faddenSoft, LLC. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
/*
* Common code for reformatters.
*/
#include "StdAfx.h"
#include "Reformat.h"
#include "ReformatBase.h"
#include "AppleWorks.h"
#include "Asm.h"
#include "AWGS.h"
#include "Basic.h"
#include "CPMFiles.h"
#include "Directory.h"
#include "Disasm.h"
#include "DoubleHiRes.h"
#include "HiRes.h"
#include "MacPaint.h"
#include "PascalFiles.h"
#include "PrintShop.h"
#include "ResourceFork.h"
#include "Simple.h"
#include "SuperHiRes.h"
#include "Teach.h"
#include "Text8.h"
/*
* Create an instance of the class identified by "id".
*/
/*static*/ Reformat* ReformatHolder::GetReformatInstance(ReformatID id)
{
Reformat* pReformat = NULL;
switch (id) {
case kReformatTextEOL_HA: pReformat = new ReformatEOL_HA; break;
case kReformatRaw: pReformat = new ReformatRaw; break;
case kReformatHexDump: pReformat = new ReformatHexDump; break;
case kReformatResourceFork: pReformat = new ReformatResourceFork; break;
case kReformatProDOSDirectory: pReformat = new ReformatDirectory; break;
case kReformatPascalText: pReformat = new ReformatPascalText; break;
case kReformatPascalCode: pReformat = new ReformatPascalCode; break;
case kReformatCPMText: pReformat = new ReformatCPMText; break;
case kReformatApplesoft: pReformat = new ReformatApplesoft; break;
case kReformatApplesoft_Hilite: pReformat = new ReformatApplesoft; break;
case kReformatInteger: pReformat = new ReformatInteger; break;
case kReformatInteger_Hilite: pReformat = new ReformatInteger; break;
case kReformatBusiness: pReformat = new ReformatBusiness; break;
case kReformatBusiness_Hilite: pReformat = new ReformatBusiness; break;
case kReformatSCAssem: pReformat = new ReformatSCAssem; break;
case kReformatMerlin: pReformat = new ReformatMerlin; break;
case kReformatLISA2: pReformat = new ReformatLISA2; break;
case kReformatLISA3: pReformat = new ReformatLISA3; break;
case kReformatLISA4: pReformat = new ReformatLISA4; break;
case kReformatMonitor8: pReformat = new ReformatDisasm8; break;
case kReformatDisasmMerlin8: pReformat = new ReformatDisasm8; break;
case kReformatMonitor16Long: pReformat = new ReformatDisasm16; break;
case kReformatMonitor16Short: pReformat = new ReformatDisasm16; break;
case kReformatDisasmOrcam16: pReformat = new ReformatDisasm16; break;
case kReformatAWGS_WP: pReformat = new ReformatAWGS_WP; break;
case kReformatTeach: pReformat = new ReformatTeach; break;
case kReformatGWP: pReformat = new ReformatGWP; break;
case kReformatMagicWindow: pReformat = new ReformatMagicWindow; break;
case kReformatAWP: pReformat = new ReformatAWP; break;
case kReformatADB: pReformat = new ReformatADB; break;
case kReformatASP: pReformat = new ReformatASP; break;
case kReformatHiRes: pReformat = new ReformatHiRes; break;
case kReformatHiRes_BW: pReformat = new ReformatHiRes; break;
case kReformatDHR_Latched: pReformat = new ReformatDHR; break;
case kReformatDHR_BW: pReformat = new ReformatDHR; break;
case kReformatDHR_Plain140: pReformat = new ReformatDHR; break;
case kReformatDHR_Window: pReformat = new ReformatDHR; break;
case kReformatSHR_PIC: pReformat = new ReformatUnpackedSHR; break;
case kReformatSHR_JEQ: pReformat = new ReformatJEQSHR; break;
case kReformatSHR_Paintworks: pReformat = new ReformatPaintworksSHR; break;
case kReformatSHR_Packed: pReformat = new ReformatPackedSHR; break;
case kReformatSHR_APF: pReformat = new ReformatAPFSHR; break;
case kReformatSHR_3200: pReformat = new Reformat3200SHR; break;
case kReformatSHR_3201: pReformat = new Reformat3201SHR; break;
case kReformatSHR_DG256: pReformat = new ReformatDG256SHR; break;
case kReformatSHR_DG3200: pReformat = new ReformatDG3200SHR; break;
case kReformatPrintShop: pReformat = new ReformatPrintShop; break;
case kReformatMacPaint: pReformat = new ReformatMacPaint; break;
case kReformatGutenberg: pReformat = new ReformatGutenberg; break;
case kReformatUnknown:
case kReformatMAX:
default: assert(false); break;
}
return pReformat;
}
/*
* Return a string describing the class identified by "id". We need this for
* the pop-up menu in the file viewer.
*
* Would have been nice to embed these in the individual classes, but that's
* harder to maintain.
*/
/*static*/ const WCHAR* ReformatHolder::GetReformatName(ReformatID id)
{
const WCHAR* descr = NULL;
switch (id) {
case kReformatTextEOL_HA:
descr = L"Converted Text";
break;
case kReformatRaw:
descr = L"Raw";
break;
case kReformatHexDump:
descr = L"Hex Dump";
break;
case kReformatResourceFork:
descr = L"Resource Fork";
break;
case kReformatProDOSDirectory:
descr = L"ProDOS Directory";
break;
case kReformatPascalText:
descr = L"Pascal Text";
break;
case kReformatPascalCode:
descr = L"Pascal Code";
break;
case kReformatCPMText:
descr = L"CP/M Text";
break;
case kReformatApplesoft:
descr = L"Applesoft BASIC";
break;
case kReformatApplesoft_Hilite:
descr = L"Applesoft BASIC w/Highlighting";
break;
case kReformatInteger:
descr = L"Integer BASIC";
break;
case kReformatInteger_Hilite:
descr = L"Integer BASIC w/Highlighting";
break;
case kReformatBusiness:
descr = L"Apple /// Business BASIC";
break;
case kReformatBusiness_Hilite:
descr = L"Apple /// Business BASIC w/Highlighting";
break;
case kReformatSCAssem:
descr = L"S-C Assembler";
break;
case kReformatMerlin:
descr = L"Merlin Assembler";
break;
case kReformatLISA2:
descr = L"LISA Assembler (v2)";
break;
case kReformatLISA3:
descr = L"LISA Assembler (v3)";
break;
case kReformatLISA4:
descr = L"LISA Assembler (v4/v5)";
break;
case kReformatMonitor8:
descr = L"//e monitor listing";
break;
case kReformatDisasmMerlin8:
descr = L"8-bit disassembly (Merlin)";
break;
case kReformatMonitor16Long:
descr = L"IIgs monitor listing (long regs)";
break;
case kReformatMonitor16Short:
descr = L"IIgs monitor listing (short regs)";
break;
case kReformatDisasmOrcam16:
descr = L"16-bit disassembly (Orca/M)";
break;
case kReformatAWGS_WP:
descr = L"AppleWorks GS Word Processor";
break;
case kReformatTeach:
descr = L"Teach Text";
break;
case kReformatGWP:
descr = L"Generic IIgs text document";
break;
case kReformatMagicWindow:
descr = L"Magic Window";
break;
case kReformatGutenberg:
descr = L"Gutenberg Word Processor";
break;
case kReformatAWP:
descr = L"AppleWorks Word Processor";
break;
case kReformatADB:
descr = L"AppleWorks Database";
break;
case kReformatASP:
descr = L"AppleWorks Spreadsheet";
break;
case kReformatHiRes:
descr = L"Hi-Res / Color";
break;
case kReformatHiRes_BW:
descr = L"Hi-Res / B&W";
break;
case kReformatDHR_Latched:
descr = L"Double Hi-Res / Latched";
break;
case kReformatDHR_BW:
descr = L"Double Hi-Res / B&W";
break;
case kReformatDHR_Plain140:
descr = L"Double Hi-Res / Plain140";
break;
case kReformatDHR_Window:
descr = L"Double Hi-Res / Windowed";
break;
case kReformatSHR_PIC:
descr = L"Super Hi-Res";
break;
case kReformatSHR_JEQ:
descr = L"JEQ Super Hi-Res";
break;
case kReformatSHR_Paintworks:
descr = L"Paintworks Super Hi-Res";
break;
case kReformatSHR_Packed:
descr = L"Packed Super Hi-Res";
break;
case kReformatSHR_APF:
descr = L"APF Super Hi-Res";
break;
case kReformatSHR_3200:
descr = L"3200-Color Super Hi-Res";
break;
case kReformatSHR_3201:
descr = L"Packed 3200-Color Super Hi-Res";
break;
case kReformatSHR_DG256:
descr = L"DreamGrafix 256-Color Super Hi-Res";
break;
case kReformatSHR_DG3200:
descr = L"DreamGrafix 3200-Color Super Hi-Res";
break;
case kReformatPrintShop:
descr = L"Print Shop Clip Art";
break;
case kReformatMacPaint:
descr = L"MacPaint";
break;
case kReformatUnknown:
case kReformatMAX:
default:
assert(false);
descr = L"UNKNOWN";
break;
}
return descr;
}
/*
* Set the file attributes. These are used by TestApplicability tests to
* decide what it is we're looking at.
*/
void ReformatHolder::SetSourceAttributes(long fileType, long auxType,
SourceFormat sourceFormat, const char* nameExt)
{
fFileType = fileType;
fAuxType = auxType;
fSourceFormat = sourceFormat;
assert(fNameExt == NULL);
if (nameExt == NULL) {
fNameExt = new char[1];
fNameExt[0] = '\0';
} else {
fNameExt = strdup(nameExt);
}
}
/*
* Run through the set of reformatters and figure out which apply.
*
* Each reformatter function is handed the full set. If it can make a
* determination for more than one entry (e.g. Applesoft and ApplesoftHilite),
* it can set all that apply. There must not be any overlap between
* reformatters -- this is here so that a single reformatter may have more
* than one entry without having to re-process the data multiple times.
*
* Before calling here, the file data and file attributes (e.g. file type)
* should be stored in "ReformatHolder".
*/
void ReformatHolder::TestApplicability(void)
{
Reformat* pReformat;
int i;
for (i = 0; i < kReformatMAX; i++) {
if (fApplies[kPartData][i] != kApplicUnknown) {
assert(fApplies[kPartRsrc][i] != kApplicUnknown);
assert(fApplies[kPartCmmt][i] != kApplicUnknown);
continue; // already set by previous test
}
if (!fAllow[i]) {
if (i != 0) {
LOGI(" NOTE: Applic %d disallowed", i);
// did you update ConfigureReformatFromPreferences()?
}
fApplies[kPartData][i] = kApplicNot;
fApplies[kPartRsrc][i] = kApplicNot;
fApplies[kPartCmmt][i] = kApplicNot;
continue;
}
/*
* Create an instance of the object, test its applicability, and
* then destroy the instance. It's less efficient to do it this
* way than some other approaches, but it's easier maintenance
* than creating a separate table of pointers to static functions.
*/
pReformat = GetReformatInstance((ReformatID) i);
assert(pReformat != NULL);
pReformat->Examine(this);
delete pReformat;
}
/* don't mess with the unknown */
assert(fApplies[kPartData][kReformatUnknown] == kApplicNot);
assert(fApplies[kPartRsrc][kReformatUnknown] == kApplicNot);
assert(fApplies[kPartCmmt][kReformatUnknown] == kApplicNot);
}
/*
* Return the appropriate applicability level.
*/
ReformatHolder::ReformatApplies ReformatHolder::GetApplic(ReformatPart part,
ReformatID id) const
{
if (id < kReformatUnknown || id >= kReformatMAX) {
assert(false);
return kApplicUnknown;
}
if (part <= kPartUnknown || part >= kPartMAX) {
assert(false);
return kApplicUnknown;
}
return fApplies[part][id];
}
/*
* Set the appropriate applicability level.
*/
void ReformatHolder::SetApplic(ReformatID id, ReformatApplies applyData,
ReformatApplies applyRsrc, ReformatApplies applyCmmt)
{
if (id <= kReformatUnknown || id >= kReformatMAX) {
assert(false);
return;
}
fApplies[kPartData][id] = applyData;
fApplies[kPartRsrc][id] = applyRsrc;
fApplies[kPartCmmt][id] = applyCmmt;
}
/*
* Set the "preferred" flag on all parts that aren't "unknown" or "not"
* for the specified id. If "part" isn't kPartUnknown, then only that
* part will be altered.
*
* The idea is to prefer one variation over another, such as highlighting
* a BASIC program or choosing a double-hi-res algorithm. The preference
* indicates the default choice, but does not exclude others.
*/
void ReformatHolder::SetApplicPreferred(ReformatID id, ReformatPart part)
{
for (int i = 0; i < kPartMAX; i++) {
if (i == kPartUnknown)
continue;
if (part != kPartUnknown && i != part)
continue;
// Don't set "preferred" flag for "not", "probably not", "always".
if (fApplies[i][id] >= kApplicAlways) {
fApplies[i][id] =
(ReformatApplies) (fApplies[i][id] | kApplicPreferred);
}
}
}
/*
* Returns <0, 0, or >0 depending on whether app1 is worse, equal to,
* or better than app2.
*/
int ReformatHolder::CompareApplies(ReformatApplies app1, ReformatApplies app2)
{
if ((app1 & kApplicPrefMask) < (app2 & kApplicPrefMask))
return -1;
else if ((app1 & kApplicPrefMask) > (app2 & kApplicPrefMask))
return 1;
else return (app1 - app2); // compare with "preferred" bit
}
/*
* Find the best reformatter for the specified part of the loaded file.
*/
ReformatHolder::ReformatID ReformatHolder::FindBest(ReformatPart part)
{
ReformatID bestID = kReformatUnknown;
ReformatApplies bestApply = kApplicNot;
ReformatApplies apply;
int i;
/* if the source couldn't be loaded, just return "raw" */
if (fErrorBuf[part] != NULL)
return kReformatRaw;
/*
* Use the best option, or an equivalent-valued option that has
* the "preferred" flag set.
*/
for (i = 0; i < kReformatMAX; i++) {
apply = GetApplic(part, (ReformatID) i);
if (CompareApplies(apply, bestApply) > 0) {
bestApply = apply;
bestID = (ReformatID) i;
}
}
if (bestID == kReformatUnknown || bestApply == kApplicNot) {
LOGW("Did you forget to call TestApplicability?");
assert(false);
return kReformatRaw;
}
LOGI("Best is %d at lvl=%d", bestID, bestApply);
return bestID;
}
/*
* Apply the requested formatter to the specified part.
*/
ReformatOutput* ReformatHolder::Apply(ReformatPart part, ReformatID id)
{
ReformatOutput* pOutput;
Reformat* pReformat;
int result;
if (id <= kReformatUnknown || id >= kReformatMAX ||
part <= kPartUnknown || part >= kPartMAX)
{
LOGW("Invalid reformat request (part=%d id=%d)", part, id);
assert(false);
return NULL;
}
/* create a place for the output */
pOutput = new ReformatOutput;
if (pOutput == NULL) {
assert(false);
return NULL; // alloc failure
}
/*
* If the caller was unable to fill our source buffer, they will have
* supplied us with an error message. Return that instead of the data.
*/
if (fErrorBuf[part] != NULL) {
pOutput->SetTextBuf(fErrorBuf[part], strlen(fErrorBuf[part]), false);
pOutput->SetOutputKind(ReformatOutput::kOutputErrorMsg);
pOutput->SetFormatDescr(L"Error Message");
return pOutput;
}
/*
* Set the format description here, based on the id. If the reformatter
* fails we will need to change this, but it allows the reformatter to
* override our label with its own in case it wants to indicate some
* sort of sub-variant.
*/
pOutput->SetFormatDescr(GetReformatName(id));
/* create an instance of a reformatter */
pReformat = GetReformatInstance(id);
assert(pReformat != NULL);
result = pReformat->Process(this, id, part, pOutput);
delete pReformat;
/*
* If it fails, return a pointer to the source buffer.
*
* This commonly happens on zero-length files. The chosen reformatter
* rejects it, returns -1, and we return the source buffer. Since even
* zero-length files are guaranteed to have some sort of allocated
* buffer, "pOutput" never points at NULL. Unless, of course, a text
* reformatter produces no output but still returns "success".
*/
if (result < 0) {
pOutput->SetTextBuf((char*)fSourceBuf[part], fSourceLen[part], false);
pOutput->SetOutputKind(ReformatOutput::kOutputRaw);
pOutput->SetFormatDescr(GetReformatName(kReformatRaw));
}
return pOutput;
}
/*
* Get the appropriate input buffer.
*/
const uint8_t* ReformatHolder::GetSourceBuf(ReformatPart part) const
{
if (part <= kPartUnknown || part >= kPartMAX) {
assert(false);
return NULL;
}
return fSourceBuf[part];
}
/*
* Get the length of the appropriate input buffer.
*/
long ReformatHolder::GetSourceLen(ReformatPart part) const
{
if (part <= kPartUnknown || part >= kPartMAX) {
assert(false);
return NULL;
}
return fSourceLen[part];
}
/*
* Set the input buffer.
*
* A buffer is required, even for empty input. This makes the overall
* housekeeping simpler.
*
* The ReformatHolder "owns" the buffer afterward, so the caller should
* discard its pointer.
*/
void ReformatHolder::SetSourceBuf(ReformatPart part, uint8_t* buf, long len)
{
if (part <= kPartUnknown || part >= kPartMAX) {
assert(false);
return;
}
assert(buf != NULL);
assert(len >= 0);
fSourceBuf[part] = buf;
fSourceLen[part] = len;
}
/*
* Specify an error message to return instead of reformatted text for a
* given part.
*/
void ReformatHolder::SetErrorMsg(ReformatPart part, const char* msg)
{
assert(msg != NULL && *msg != '\0');
assert(part > kPartUnknown && part < kPartMAX);
assert(fErrorBuf[part] == NULL);
fErrorBuf[part] = strdup(msg);
LOGI("+++ set error message for part %d to '%hs'", part, msg);
}
void ReformatHolder::SetErrorMsg(ReformatPart part, const CString& str)
{
CStringA stra(str);
SetErrorMsg(part, (LPCSTR) stra);
}