mirror of
https://github.com/fadden/ciderpress.git
synced 2024-11-23 11:33:58 +00:00
595 lines
16 KiB
C++
595 lines
16 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 = nil;
|
|
|
|
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 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 char*
|
|
ReformatHolder::GetReformatName(ReformatID id)
|
|
{
|
|
const char* descr = nil;
|
|
|
|
switch (id) {
|
|
case kReformatTextEOL_HA:
|
|
descr = "Converted Text";
|
|
break;
|
|
case kReformatRaw:
|
|
descr = "Raw";
|
|
break;
|
|
case kReformatHexDump:
|
|
descr = "Hex Dump";
|
|
break;
|
|
case kReformatResourceFork:
|
|
descr = "Resource Fork";
|
|
break;
|
|
case kReformatProDOSDirectory:
|
|
descr = "ProDOS Directory";
|
|
break;
|
|
case kReformatPascalText:
|
|
descr = "Pascal Text";
|
|
break;
|
|
case kReformatPascalCode:
|
|
descr = "Pascal Code";
|
|
break;
|
|
case kReformatCPMText:
|
|
descr = "CP/M Text";
|
|
break;
|
|
case kReformatApplesoft:
|
|
descr = "Applesoft BASIC";
|
|
break;
|
|
case kReformatApplesoft_Hilite:
|
|
descr = "Applesoft BASIC w/Highlighting";
|
|
break;
|
|
case kReformatInteger:
|
|
descr = "Integer BASIC";
|
|
break;
|
|
case kReformatInteger_Hilite:
|
|
descr = "Integer BASIC w/Highlighting";
|
|
break;
|
|
case kReformatBusiness:
|
|
descr = "Apple /// Business BASIC";
|
|
break;
|
|
case kReformatBusiness_Hilite:
|
|
descr = "Apple /// Business BASIC w/Highlighting";
|
|
break;
|
|
case kReformatSCAssem:
|
|
descr = "S-C Assembler";
|
|
break;
|
|
case kReformatMerlin:
|
|
descr = "Merlin Assembler";
|
|
break;
|
|
case kReformatLISA2:
|
|
descr = "LISA Assembler (v2)";
|
|
break;
|
|
case kReformatLISA3:
|
|
descr = "LISA Assembler (v3)";
|
|
break;
|
|
case kReformatLISA4:
|
|
descr = "LISA Assembler (v4/v5)";
|
|
break;
|
|
case kReformatMonitor8:
|
|
descr = "//e monitor listing";
|
|
break;
|
|
case kReformatDisasmMerlin8:
|
|
descr = "8-bit disassembly (Merlin)";
|
|
break;
|
|
case kReformatMonitor16Long:
|
|
descr = "IIgs monitor listing (long regs)";
|
|
break;
|
|
case kReformatMonitor16Short:
|
|
descr = "IIgs monitor listing (short regs)";
|
|
break;
|
|
case kReformatDisasmOrcam16:
|
|
descr = "16-bit disassembly (Orca/M)";
|
|
break;
|
|
case kReformatAWGS_WP:
|
|
descr = "AppleWorks GS Word Processor";
|
|
break;
|
|
case kReformatTeach:
|
|
descr = "Teach Text";
|
|
break;
|
|
case kReformatGWP:
|
|
descr = "Generic IIgs text document";
|
|
break;
|
|
case kReformatMagicWindow:
|
|
descr = "Magic Window";
|
|
break;
|
|
case kReformatAWP:
|
|
descr = "AppleWorks Word Processor";
|
|
break;
|
|
case kReformatADB:
|
|
descr = "AppleWorks Database";
|
|
break;
|
|
case kReformatASP:
|
|
descr = "AppleWorks Spreadsheet";
|
|
break;
|
|
case kReformatHiRes:
|
|
descr = "Hi-Res / Color";
|
|
break;
|
|
case kReformatHiRes_BW:
|
|
descr = "Hi-Res / B&W";
|
|
break;
|
|
case kReformatDHR_Latched:
|
|
descr = "Double Hi-Res / Latched";
|
|
break;
|
|
case kReformatDHR_BW:
|
|
descr = "Double Hi-Res / B&W";
|
|
break;
|
|
case kReformatDHR_Plain140:
|
|
descr = "Double Hi-Res / Plain140";
|
|
break;
|
|
case kReformatDHR_Window:
|
|
descr = "Double Hi-Res / Windowed";
|
|
break;
|
|
case kReformatSHR_PIC:
|
|
descr = "Super Hi-Res";
|
|
break;
|
|
case kReformatSHR_JEQ:
|
|
descr = "JEQ Super Hi-Res";
|
|
break;
|
|
case kReformatSHR_Paintworks:
|
|
descr = "Paintworks Super Hi-Res";
|
|
break;
|
|
case kReformatSHR_Packed:
|
|
descr = "Packed Super Hi-Res";
|
|
break;
|
|
case kReformatSHR_APF:
|
|
descr = "APF Super Hi-Res";
|
|
break;
|
|
case kReformatSHR_3200:
|
|
descr = "3200-Color Super Hi-Res";
|
|
break;
|
|
case kReformatSHR_3201:
|
|
descr = "Packed 3200-Color Super Hi-Res";
|
|
break;
|
|
case kReformatSHR_DG256:
|
|
descr = "DreamGrafix 256-Color Super Hi-Res";
|
|
break;
|
|
case kReformatSHR_DG3200:
|
|
descr = "DreamGrafix 3200-Color Super Hi-Res";
|
|
break;
|
|
case kReformatPrintShop:
|
|
descr = "Print Shop Clip Art";
|
|
break;
|
|
case kReformatMacPaint:
|
|
descr = "MacPaint";
|
|
break;
|
|
case kReformatUnknown:
|
|
case kReformatMAX:
|
|
default:
|
|
assert(false);
|
|
descr = "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;
|
|
|
|
if (nameExt == nil) {
|
|
assert(fNameExt == nil);
|
|
fNameExt = new char[1];
|
|
fNameExt[0] = '\0';
|
|
} else {
|
|
fNameExt = new char[strlen(nameExt)+1];
|
|
strcpy(fNameExt, 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) {
|
|
WMSG1(" NOTE: Applic %d disallowed\n", 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 != nil);
|
|
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] != nil)
|
|
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) {
|
|
WMSG0("Did you forget to call TestApplicability?\n");
|
|
assert(false);
|
|
return kReformatRaw;
|
|
}
|
|
|
|
WMSG2("Best is %d at lvl=%d\n", 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)
|
|
{
|
|
WMSG2("Invalid reformat request (part=%d id=%d)\n", part, id);
|
|
assert(false);
|
|
return nil;
|
|
}
|
|
|
|
/* create a place for the output */
|
|
pOutput = new ReformatOutput;
|
|
if (pOutput == nil) {
|
|
assert(false);
|
|
return nil; // 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] != nil) {
|
|
pOutput->SetTextBuf(fErrorBuf[part], strlen(fErrorBuf[part]), false);
|
|
pOutput->SetOutputKind(ReformatOutput::kOutputErrorMsg);
|
|
pOutput->SetFormatDescr(_T("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 != nil);
|
|
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 nil. 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 unsigned char*
|
|
ReformatHolder::GetSourceBuf(ReformatPart part) const
|
|
{
|
|
if (part <= kPartUnknown || part >= kPartMAX) {
|
|
assert(false);
|
|
return nil;
|
|
}
|
|
|
|
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 nil;
|
|
}
|
|
|
|
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, unsigned char* buf,
|
|
long len)
|
|
{
|
|
if (part <= kPartUnknown || part >= kPartMAX) {
|
|
assert(false);
|
|
return;
|
|
}
|
|
assert(buf != nil);
|
|
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 != nil && *msg != '\0');
|
|
assert(part > kPartUnknown && part < kPartMAX);
|
|
assert(fErrorBuf[part] == nil);
|
|
|
|
fErrorBuf[part] = new char[strlen(msg) + 1];
|
|
if (fErrorBuf[part] != nil) {
|
|
WMSG2("+++ set error message for part %d to '%s'\n", part, msg);
|
|
strcpy(fErrorBuf[part], msg);
|
|
}
|
|
}
|