mirror of
https://github.com/jmarshall23/msword.git
synced 2026-08-11 23:50:53 +02:00
Better Docx support.
This commit is contained in:
@@ -517,9 +517,28 @@ LSplat2:
|
||||
rcw.ywBottom = max(rcw.ywTop, rcw.ywBottom - dyFrameLine);
|
||||
}
|
||||
|
||||
#ifdef OPUS_X64
|
||||
{
|
||||
int iPageClip = 0;
|
||||
/* The native continuous-page shell keeps rcePage in client pixel
|
||||
coordinates. Some legacy opaque-line erases widen their rectangle
|
||||
after ClipRectFromDr, which can otherwise paint white bands into the
|
||||
gray workspace. A real DC clip is the final safety boundary. */
|
||||
if (pwwd->fPageView && (iPageClip = SaveDC(hdc)) != 0)
|
||||
IntersectClipRect(hdc, pwwd->rcePage.xeLeft,
|
||||
pwwd->rcePage.yeTop, pwwd->rcePage.xeRight,
|
||||
pwwd->rcePage.yeBottom);
|
||||
DisplayFliCore(ww, rcw,
|
||||
dxpToXw + /* aesthetic fudge */ (fWin ? 0 : vsci.dxpBorder),
|
||||
rcwErase.ywBottom);
|
||||
if (iPageClip != 0)
|
||||
RestoreDC(hdc, iPageClip);
|
||||
}
|
||||
#else
|
||||
DisplayFliCore(ww, rcw,
|
||||
dxpToXw + /* aesthetic fudge */ (fWin ? 0 : vsci.dxpBorder),
|
||||
rcwErase.ywBottom);
|
||||
#endif
|
||||
|
||||
/* edl may have changed in drawing picture in displayflicore. Update. */
|
||||
GetPlc(hplcedl, dl, &edl);
|
||||
|
||||
@@ -34,6 +34,7 @@ DEBUGASSERTSZ /* WIN - bogus macro for assert string */
|
||||
#include "dlbenum.h"
|
||||
#include "error.h"
|
||||
#include "prompt.h"
|
||||
#include "border.h"
|
||||
|
||||
#ifdef DEBUG
|
||||
#ifdef PCJ
|
||||
@@ -85,6 +86,13 @@ extern KMP ** hkmpCur;
|
||||
extern KMP ** vhkmpUser;
|
||||
#ifdef OPUS_X64
|
||||
extern int OpusWin95OpenAliasIsDocx();
|
||||
extern int OpusModernPendingDocxRunCount();
|
||||
extern int OpusModernPendingDocxParagraphCount();
|
||||
extern int OpusModernGetPendingDocxRun();
|
||||
extern int OpusModernGetPendingDocxParagraph();
|
||||
extern int OpusModernGetPendingDocxPage();
|
||||
extern void OpusModernClearPendingDocxFormatting();
|
||||
extern int OpusX64FtcFromFontNameForDoc();
|
||||
#endif
|
||||
|
||||
struct DTTM DttmCur();
|
||||
@@ -94,6 +102,133 @@ static csconst char rgchRTF[] = {
|
||||
'{', '\\','r','t','f'};
|
||||
|
||||
|
||||
#ifdef OPUS_X64
|
||||
/* Apply the OOXML properties recorded while the temporary plain-text file was
|
||||
created. Importing the generated RTF through the 16-bit-era parser is not
|
||||
reliable on x64, but the normal Word property engine is: this keeps the
|
||||
stable text-file open path and restores formatting to the resulting doc. */
|
||||
static int CbAppendDocxPrl(grpprl, cb, cbMax, sprm, val)
|
||||
char *grpprl;
|
||||
int cb, cbMax, sprm, val;
|
||||
{
|
||||
int cch;
|
||||
if (cb > cbMax - 3)
|
||||
return cb;
|
||||
cch = CchPrlFromSprmVal(grpprl + cb, sprm, val);
|
||||
return cch == valNil ? cb : cb + cch;
|
||||
}
|
||||
|
||||
|
||||
static void ApplyPendingDocxFormatting(doc)
|
||||
int doc;
|
||||
{
|
||||
int i, c, cb, ftc;
|
||||
long cpFirst, cpLim;
|
||||
CP cpMac;
|
||||
struct CA ca;
|
||||
char grpprl[96];
|
||||
char szFont[LF_FACESIZE];
|
||||
int bold, italic, underline, strike, smallCaps, allCaps, hidden;
|
||||
int hps, ico;
|
||||
int jc, dxaLeft, dxaRight, dxaLeft1, dyaBefore, dyaAfter, dyaLine;
|
||||
int fKeep, fKeepFollow, fPageBreakBefore, fBottomBorder;
|
||||
int xaPage, yaPage, dxaLeftPage, dxaRightPage, dyaTopPage, dyaBottomPage;
|
||||
struct DOD *pdod;
|
||||
|
||||
cpMac = CpMacDocEdit(doc);
|
||||
if (OpusModernGetPendingDocxPage(&xaPage, &yaPage, &dxaLeftPage,
|
||||
&dxaRightPage, &dyaTopPage, &dyaBottomPage))
|
||||
{
|
||||
pdod = PdodDoc(doc);
|
||||
pdod->dop.xaPage = xaPage;
|
||||
pdod->dop.yaPage = yaPage;
|
||||
pdod->dop.dxaLeft = dxaLeftPage;
|
||||
pdod->dop.dxaRight = dxaRightPage;
|
||||
pdod->dop.dyaTop = dyaTopPage;
|
||||
pdod->dop.dyaBottom = dyaBottomPage;
|
||||
pdod->fEnvDirty = fTrue;
|
||||
pdod->fLRDirty = fTrue;
|
||||
InvalPageView(doc);
|
||||
InvalDoc(doc);
|
||||
InvalFli();
|
||||
}
|
||||
c = OpusModernPendingDocxParagraphCount();
|
||||
for (i = 0; i < c; ++i)
|
||||
{
|
||||
if (!OpusModernGetPendingDocxParagraph(i, &cpFirst, &cpLim,
|
||||
&jc, &dxaLeft, &dxaRight, &dxaLeft1, &dyaBefore,
|
||||
&dyaAfter, &dyaLine, &fKeep, &fKeepFollow,
|
||||
&fPageBreakBefore, &fBottomBorder))
|
||||
continue;
|
||||
if (cpFirst < cp0 || cpFirst > cpMac)
|
||||
continue;
|
||||
if (cpLim > cpMac)
|
||||
cpLim = cpMac;
|
||||
if (cpLim <= cpFirst)
|
||||
cpLim = min(cpMac, cpFirst + ccpEop);
|
||||
if (cpLim <= cpFirst)
|
||||
continue;
|
||||
cb = 0;
|
||||
cb = CbAppendDocxPrl(grpprl, cb, sizeof(grpprl), sprmPJc, jc);
|
||||
cb = CbAppendDocxPrl(grpprl, cb, sizeof(grpprl), sprmPDxaLeft, dxaLeft);
|
||||
cb = CbAppendDocxPrl(grpprl, cb, sizeof(grpprl), sprmPDxaRight, dxaRight);
|
||||
cb = CbAppendDocxPrl(grpprl, cb, sizeof(grpprl), sprmPDxaLeft1, dxaLeft1);
|
||||
cb = CbAppendDocxPrl(grpprl, cb, sizeof(grpprl), sprmPDyaBefore, dyaBefore);
|
||||
cb = CbAppendDocxPrl(grpprl, cb, sizeof(grpprl), sprmPDyaAfter, dyaAfter);
|
||||
if (dyaLine != 0)
|
||||
cb = CbAppendDocxPrl(grpprl, cb, sizeof(grpprl), sprmPDyaLine, dyaLine);
|
||||
cb = CbAppendDocxPrl(grpprl, cb, sizeof(grpprl), sprmPFKeep, fKeep);
|
||||
cb = CbAppendDocxPrl(grpprl, cb, sizeof(grpprl), sprmPFKeepFollow, fKeepFollow);
|
||||
cb = CbAppendDocxPrl(grpprl, cb, sizeof(grpprl),
|
||||
sprmPFPageBreakBefore, fPageBreakBefore);
|
||||
if (fBottomBorder)
|
||||
cb = CbAppendDocxPrl(grpprl, cb, sizeof(grpprl),
|
||||
sprmPBrcBottom, brcSingle | (dptBrcSpace << shiftDxpSpaceBrc));
|
||||
ca.doc = doc;
|
||||
ca.cpFirst = cpFirst;
|
||||
ca.cpLim = cpLim;
|
||||
ApplyGrpprlCa(grpprl, cb, &ca);
|
||||
}
|
||||
|
||||
c = OpusModernPendingDocxRunCount();
|
||||
for (i = 0; i < c; ++i)
|
||||
{
|
||||
if (!OpusModernGetPendingDocxRun(i, &cpFirst, &cpLim, &bold,
|
||||
&italic, &underline, &strike, &smallCaps, &allCaps,
|
||||
&hidden, &hps, &ico, szFont, sizeof(szFont)))
|
||||
continue;
|
||||
if (cpFirst < cp0 || cpFirst >= cpMac)
|
||||
continue;
|
||||
if (cpLim > cpMac)
|
||||
cpLim = cpMac;
|
||||
if (cpLim <= cpFirst)
|
||||
continue;
|
||||
cb = 0;
|
||||
cb = CbAppendDocxPrl(grpprl, cb, sizeof(grpprl), sprmCFBold, bold);
|
||||
cb = CbAppendDocxPrl(grpprl, cb, sizeof(grpprl), sprmCFItalic, italic);
|
||||
cb = CbAppendDocxPrl(grpprl, cb, sizeof(grpprl), sprmCKul,
|
||||
underline ? kulSingle : kulNone);
|
||||
cb = CbAppendDocxPrl(grpprl, cb, sizeof(grpprl), sprmCFStrike, strike);
|
||||
cb = CbAppendDocxPrl(grpprl, cb, sizeof(grpprl), sprmCFSmallCaps, smallCaps);
|
||||
cb = CbAppendDocxPrl(grpprl, cb, sizeof(grpprl), sprmCFCaps, allCaps);
|
||||
cb = CbAppendDocxPrl(grpprl, cb, sizeof(grpprl), sprmCFVanish, hidden);
|
||||
cb = CbAppendDocxPrl(grpprl, cb, sizeof(grpprl), sprmCHps, hps);
|
||||
cb = CbAppendDocxPrl(grpprl, cb, sizeof(grpprl), sprmCIco, ico);
|
||||
ftc = OpusX64FtcFromFontNameForDoc(doc, szFont);
|
||||
if (ftc >= 0)
|
||||
cb = CbAppendDocxPrl(grpprl, cb, sizeof(grpprl), sprmCFtc, ftc);
|
||||
ca.doc = doc;
|
||||
ca.cpFirst = cpFirst;
|
||||
ca.cpLim = cpLim;
|
||||
ApplyGrpprlCa(grpprl, cb, &ca);
|
||||
}
|
||||
|
||||
PdodDoc(doc)->fFormatted = fTrue;
|
||||
OpusModernClearPendingDocxFormatting();
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
/* used to convert between versions of the fib */
|
||||
ConvertFib (pfib)
|
||||
struct FIB *pfib;
|
||||
@@ -529,10 +664,16 @@ LConvert:
|
||||
|
||||
case dffOpenText:
|
||||
if ((doc = DocReadPlainFn (fn)) != docNil)
|
||||
{
|
||||
if (dff == dffOpenText)
|
||||
PfcbFn(fn)->fForeignFormat = fFalse;
|
||||
else
|
||||
PdodDoc(doc)->fn = fnT;
|
||||
#ifdef OPUS_X64
|
||||
if (OpusWin95OpenAliasIsDocx(PfcbFn(fn)->stFile))
|
||||
ApplyPendingDocxFormatting(doc);
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
+70
-51
@@ -50,7 +50,9 @@ extern RRF rrf;
|
||||
extern struct WWD ** hwwdCur;
|
||||
extern struct MWD ** hmwdCur;
|
||||
extern struct SEL selCur;
|
||||
extern struct CHP vchpFetch;
|
||||
extern struct CHP vchpGraySelCur;
|
||||
extern struct PAP vpapFetch;
|
||||
extern struct PAP vpapSelCur;
|
||||
extern struct PAP vpapGraySelCur;
|
||||
extern struct SCI vsci;
|
||||
@@ -106,17 +108,17 @@ static int vWin95BaseDysInch = 0;
|
||||
int OpusExportCurrentDocumentPdf()
|
||||
{
|
||||
int doc;
|
||||
HANDLE hText;
|
||||
LPCH lpch;
|
||||
int result;
|
||||
int cchText = 0;
|
||||
int fAfterReturn = fFalse;
|
||||
CP cp;
|
||||
CP cpPara;
|
||||
CP cpMac;
|
||||
struct DOD *pdod;
|
||||
extern int vccpFetch;
|
||||
extern CHAR HUGE *vhpchFetch;
|
||||
extern int N_FetchCp();
|
||||
extern int OpusExportTextToPdfDialog();
|
||||
extern int OpusPdfSnapshotBegin();
|
||||
extern int OpusPdfSnapshotAddParagraph();
|
||||
extern int OpusPdfSnapshotAddRun();
|
||||
extern int OpusPdfSnapshotExportDialog();
|
||||
extern void OpusX64FontNameFromFtc();
|
||||
|
||||
if (selCur.doc == docNil)
|
||||
return fFalse;
|
||||
@@ -124,61 +126,78 @@ int OpusExportCurrentDocumentPdf()
|
||||
cpMac = CpMacDocEdit(doc);
|
||||
if (cpMac < cp0 || cpMac > 0x3fffffffL)
|
||||
return fFalse;
|
||||
hText = GlobalAlloc(GMEM_MOVEABLE, (DWORD)(cpMac * 2L + 1L));
|
||||
if (hText == hNil || (lpch = GlobalLockClip(hText)) == NULL)
|
||||
{
|
||||
if (hText != hNil)
|
||||
GlobalFree(hText);
|
||||
pdod = PdodMother(doc);
|
||||
if (!OpusPdfSnapshotBegin(pdod->dop.xaPage, pdod->dop.yaPage,
|
||||
pdod->dop.dxaLeft, pdod->dop.dxaRight,
|
||||
pdod->dop.dyaTop < 0 ? -pdod->dop.dyaTop : pdod->dop.dyaTop,
|
||||
pdod->dop.dyaBottom < 0 ? -pdod->dop.dyaBottom : pdod->dop.dyaBottom))
|
||||
return fFalse;
|
||||
}
|
||||
|
||||
for (cp = cp0; cp < cpMac; )
|
||||
for (cpPara = cp0; cpPara < cpMac; )
|
||||
{
|
||||
int ich;
|
||||
int cch;
|
||||
FetchCp(doc, cp, fcmChars);
|
||||
if (vccpFetch <= 0 || vhpchFetch == NULL)
|
||||
{
|
||||
GlobalUnlock(hText);
|
||||
GlobalFree(hText);
|
||||
CP cpParaLim;
|
||||
CP cpRun;
|
||||
CachePara(doc, cpPara);
|
||||
cpParaLim = CpMin(caPara.cpLim, cpMac);
|
||||
if (cpParaLim <= cpPara)
|
||||
cpParaLim = cpPara + 1;
|
||||
if (!OpusPdfSnapshotAddParagraph(vpapFetch.jc,
|
||||
vpapFetch.dxaLeft, vpapFetch.dxaRight,
|
||||
vpapFetch.dxaLeft1, vpapFetch.dyaBefore,
|
||||
vpapFetch.dyaAfter, vpapFetch.dyaLine,
|
||||
vpapFetch.fKeep, vpapFetch.fKeepFollow,
|
||||
vpapFetch.fPageBreakBefore,
|
||||
vpapFetch.brcBottom != 0))
|
||||
return fFalse;
|
||||
}
|
||||
cch = (int)CpMin((CP)vccpFetch, cpMac - cp);
|
||||
for (ich = 0; ich < cch; ++ich)
|
||||
|
||||
for (cpRun = cpPara; cpRun < cpParaLim; )
|
||||
{
|
||||
int ch = (unsigned char)vhpchFetch[ich];
|
||||
if (ch == chReturn)
|
||||
int ich;
|
||||
int cch;
|
||||
int cchOutput;
|
||||
char rgch[1024];
|
||||
char szFont[LF_FACESIZE];
|
||||
FetchCp(doc, cpRun, fcmChars + fcmProps);
|
||||
if (vccpFetch <= 0 || vhpchFetch == NULL)
|
||||
return fFalse;
|
||||
cch = (int)CpMin((CP)vccpFetch, cpParaLim - cpRun);
|
||||
cch = min(cch, sizeof(rgch) - 1);
|
||||
cchOutput = 0;
|
||||
for (ich = 0; ich < cch; ++ich)
|
||||
{
|
||||
lpch[cchText++] = '\r';
|
||||
lpch[cchText++] = '\n';
|
||||
fAfterReturn = fTrue;
|
||||
}
|
||||
else if (ch == chEop)
|
||||
{
|
||||
if (!fAfterReturn)
|
||||
{
|
||||
lpch[cchText++] = '\r';
|
||||
lpch[cchText++] = '\n';
|
||||
}
|
||||
fAfterReturn = fFalse;
|
||||
}
|
||||
else
|
||||
{
|
||||
fAfterReturn = fFalse;
|
||||
int ch = (unsigned char)vhpchFetch[ich];
|
||||
if (ch == chReturn || ch == chEop)
|
||||
continue;
|
||||
if (ch == chTable || ch == chTab)
|
||||
lpch[cchText++] = '\t';
|
||||
rgch[cchOutput++] = '\t';
|
||||
else if (ch == chSect || ch == chColumnBreak)
|
||||
lpch[cchText++] = '\f';
|
||||
else if (ch >= chSpace || ch == chNonReqHyphen)
|
||||
lpch[cchText++] = (char)ch;
|
||||
rgch[cchOutput++] = '\f';
|
||||
else if (ch == chNonReqHyphen)
|
||||
rgch[cchOutput++] = '-';
|
||||
/* fSpec can cover imported field/list runs whose bytes are still
|
||||
ordinary visible text. Filter actual control codes above, not
|
||||
the whole run, or leading words disappear from PDF output. */
|
||||
else if (ch >= chSpace)
|
||||
rgch[cchOutput++] = (char)ch;
|
||||
}
|
||||
if (cchOutput > 0)
|
||||
{
|
||||
rgch[cchOutput] = '\0';
|
||||
OpusX64FontNameFromFtc(vchpFetch.ftc,
|
||||
szFont, sizeof(szFont));
|
||||
if (!OpusPdfSnapshotAddRun(rgch, cchOutput, szFont,
|
||||
vchpFetch.hps, vchpFetch.fBold,
|
||||
vchpFetch.fItalic, vchpFetch.kul != kulNone,
|
||||
vchpFetch.fStrike, vchpFetch.fSmallCaps,
|
||||
vchpFetch.fCaps, vchpFetch.fVanish,
|
||||
vchpFetch.ico))
|
||||
return fFalse;
|
||||
}
|
||||
cpRun += cch;
|
||||
}
|
||||
cp += cch;
|
||||
cpPara = cpParaLim;
|
||||
}
|
||||
lpch[cchText] = '\0';
|
||||
result = OpusExportTextToPdfDialog(vhwndApp, lpch, cchText);
|
||||
GlobalUnlock(hText);
|
||||
GlobalFree(hText);
|
||||
result = OpusPdfSnapshotExportDialog(vhwndApp);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -980,7 +980,7 @@ const char *sz;
|
||||
{
|
||||
int cch;
|
||||
struct FFN *pffn;
|
||||
char rgch[cbFfnLast];
|
||||
char rgch[cbFfnLast] = {0};
|
||||
|
||||
pffn = (struct FFN *)rgch;
|
||||
if (sz == NULL || *sz == '\0')
|
||||
@@ -999,6 +999,32 @@ const char *sz;
|
||||
return FtcValidateFont(pffn);
|
||||
}
|
||||
|
||||
/* Variant used while a DOCX is being opened. The new document has not yet
|
||||
become selCur.doc, so the normal ribbon adapter would add the font to the
|
||||
document that was active before File Open. */
|
||||
EXPORT int OpusX64FtcFromFontNameForDoc(doc, sz)
|
||||
int doc;
|
||||
const char *sz;
|
||||
{
|
||||
if (sz == NULL || *sz == '\0')
|
||||
return wNinch;
|
||||
/* Word 1.x keeps only three guaranteed GDI font slots in a new plain-text
|
||||
document: Times, Symbol, and Helvetica. Adding Office-era fonts before
|
||||
the imported document becomes current can alias the new ftc to Symbol.
|
||||
Preserve the requested family safely; Helvetica is the closest available
|
||||
substitute for Aptos/Calibri/Arial and retains ANSI character mapping. */
|
||||
if (!FNeNcSz(sz, "Symbol") || !FNeNcSz(sz, "Wingdings"))
|
||||
return 1;
|
||||
if (!FNeNcSz(sz, "Times New Roman") || !FNeNcSz(sz, "Times") ||
|
||||
!FNeNcSz(sz, "Georgia") || !FNeNcSz(sz, "Garamond") ||
|
||||
!FNeNcSz(sz, "Cambria"))
|
||||
return ftcDefault;
|
||||
if (!FNeNcSz(sz, "Courier") || !FNeNcSz(sz, "Courier New") ||
|
||||
!FNeNcSz(sz, "Consolas"))
|
||||
return FtcFromDocIbst(doc, ibstCourier);
|
||||
return 2;
|
||||
}
|
||||
|
||||
EXPORT int OpusX64HpsFromFontSize(sz)
|
||||
const char *sz;
|
||||
{
|
||||
|
||||
@@ -95,6 +95,7 @@ struct CA caTable;
|
||||
extern struct FTI vfti;
|
||||
extern char (**vhgrpchr)[];
|
||||
extern int vbchrMac;
|
||||
extern struct STTB **vhsttbFont;
|
||||
#endif
|
||||
extern struct FCB **mpfnhfcb[];
|
||||
extern int vfnPreload;
|
||||
@@ -1767,6 +1768,67 @@ LONG lParam;
|
||||
return (LRESULT)(pkme != 0 && pkme->kt == ktMacro
|
||||
? pkme->bcm : -1);
|
||||
}
|
||||
case 87:
|
||||
case 88:
|
||||
case 89:
|
||||
case 90:
|
||||
{
|
||||
CP cp = (CP)lParam;
|
||||
if (cp < cp0 || cp >= CpMacDocEdit(selCur.doc))
|
||||
return (LRESULT)-1;
|
||||
FetchCp(selCur.doc, cp, fcmProps);
|
||||
if (LOWORD(wParam) == 87) return (LRESULT)(vchpFetch.fBold != 0);
|
||||
if (LOWORD(wParam) == 88) return (LRESULT)(vchpFetch.fItalic != 0);
|
||||
if (LOWORD(wParam) == 89) return (LRESULT)vchpFetch.kul;
|
||||
return (LRESULT)vchpFetch.ico;
|
||||
}
|
||||
case 91:
|
||||
case 92:
|
||||
case 93:
|
||||
case 94:
|
||||
case 95:
|
||||
case 96:
|
||||
{
|
||||
CP cp = (CP)lParam;
|
||||
if (cp < cp0 || cp >= CpMacDocEdit(selCur.doc))
|
||||
return (LRESULT)-1;
|
||||
CachePara(selCur.doc, cp);
|
||||
if (LOWORD(wParam) == 91) return (LRESULT)vpapFetch.jc;
|
||||
if (LOWORD(wParam) == 92) return (LRESULT)vpapFetch.dxaLeft;
|
||||
if (LOWORD(wParam) == 93) return (LRESULT)vpapFetch.dyaBefore;
|
||||
if (LOWORD(wParam) == 94) return (LRESULT)vpapFetch.dyaAfter;
|
||||
if (LOWORD(wParam) == 95) return (LRESULT)vpapFetch.brcBottom;
|
||||
return (LRESULT)(vpapFetch.fKeepFollow != 0);
|
||||
}
|
||||
case 97:
|
||||
return (LRESULT)PdodMother(selCur.doc)->dop.xaPage;
|
||||
case 98:
|
||||
return (LRESULT)PdodMother(selCur.doc)->dop.yaPage;
|
||||
case 99:
|
||||
return (LRESULT)PdodMother(selCur.doc)->dop.dxaLeft;
|
||||
case 100:
|
||||
return (LRESULT)PdodMother(selCur.doc)->dop.dxaRight;
|
||||
case 101:
|
||||
return (LRESULT)PdodMother(selCur.doc)->dop.dyaTop;
|
||||
case 102:
|
||||
return (LRESULT)PdodMother(selCur.doc)->dop.dyaBottom;
|
||||
case 103:
|
||||
case 104:
|
||||
{
|
||||
int ibst;
|
||||
struct FFN *pffn;
|
||||
CP cp = (CP)lParam;
|
||||
if (cp < cp0 || cp >= CpMacDocEdit(selCur.doc))
|
||||
return (LRESULT)-1;
|
||||
FetchCp(selCur.doc, cp, fcmProps);
|
||||
ibst = IbstFontFromFtcDoc(vchpFetch.ftc, selCur.doc);
|
||||
if (LOWORD(wParam) == 103)
|
||||
return (LRESULT)ibst;
|
||||
if (ibst < 0)
|
||||
return (LRESULT)-1;
|
||||
pffn = (struct FFN *)PstFromSttb(vhsttbFont, ibst);
|
||||
return (LRESULT)(unsigned char)ChsPffn(pffn);
|
||||
}
|
||||
}
|
||||
return (LRESULT) -1;
|
||||
#endif
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,86 @@
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
extern "C" int OpusModernDocxToRtfFile(const char*, const char*);
|
||||
extern "C" int OpusModernRtfFileToDocx(const char*, const char*);
|
||||
extern "C" int OpusModernRtfFileToPdf(const char*, const char*);
|
||||
|
||||
int main(const int argument_count, char** arguments) {
|
||||
if (argument_count == 3) {
|
||||
const bool converted =
|
||||
OpusModernDocxToRtfFile(arguments[1], arguments[2]) != 0;
|
||||
std::cout << "DOCX import " << (converted ? "passed" : "failed")
|
||||
<< '\n';
|
||||
return converted ? 0 : 3;
|
||||
}
|
||||
if (argument_count != 1) {
|
||||
std::cerr << "usage: opus_modern_formats_test [input.docx output.rtf]\n";
|
||||
return 1;
|
||||
}
|
||||
char temporary[MAX_PATH]{};
|
||||
char seed[MAX_PATH]{};
|
||||
if (GetTempPathA(MAX_PATH, temporary) == 0 ||
|
||||
GetTempFileNameA(temporary, "OWF", 0, seed) == 0) {
|
||||
return 1;
|
||||
}
|
||||
DeleteFileA(seed);
|
||||
const std::string base = seed;
|
||||
const std::string rtf = base + ".rtf";
|
||||
const std::string docx = base + ".docx";
|
||||
const std::string roundtrip = base + ".roundtrip.rtf";
|
||||
char requested_pdf[32768]{};
|
||||
const DWORD requested_pdf_length = GetEnvironmentVariableA(
|
||||
"WORD1_TEST_KEEP_PDF", requested_pdf,
|
||||
static_cast<DWORD>(std::size(requested_pdf)));
|
||||
const bool keep_pdf = requested_pdf_length > 0 &&
|
||||
requested_pdf_length < std::size(requested_pdf);
|
||||
const std::string pdf = keep_pdf ? requested_pdf : base + ".pdf";
|
||||
DeleteFileA(pdf.c_str());
|
||||
{
|
||||
std::ofstream output(rtf, std::ios::binary);
|
||||
output << "{\\rtf1\\ansi{\\fonttbl{\\f0 Arial;}}"
|
||||
"{\\colortbl;\\red255\\green0\\blue0;}"
|
||||
"\\f0\\fs24 Plain {\\b Bold} {\\i Italic} "
|
||||
"{\\ul Underline} {\\cf1 Red}\\par Second paragraph}";
|
||||
}
|
||||
const bool written = OpusModernRtfFileToDocx(rtf.c_str(), docx.c_str()) != 0;
|
||||
const bool read = written &&
|
||||
OpusModernDocxToRtfFile(docx.c_str(), roundtrip.c_str()) != 0;
|
||||
const bool pdf_written = OpusModernRtfFileToPdf(rtf.c_str(), pdf.c_str()) != 0;
|
||||
std::string result;
|
||||
if (read) {
|
||||
std::ifstream input(roundtrip, std::ios::binary);
|
||||
result.assign(std::istreambuf_iterator<char>(input), {});
|
||||
}
|
||||
std::string pdf_data;
|
||||
if (pdf_written) {
|
||||
std::ifstream input(pdf, std::ios::binary);
|
||||
pdf_data.assign(std::istreambuf_iterator<char>(input), {});
|
||||
}
|
||||
DeleteFileA(rtf.c_str());
|
||||
DeleteFileA(docx.c_str());
|
||||
DeleteFileA(roundtrip.c_str());
|
||||
if (!keep_pdf) DeleteFileA(pdf.c_str());
|
||||
if (!written || !read || result.find("Bold") == std::string::npos ||
|
||||
result.find("Second paragraph") == std::string::npos ||
|
||||
result.find("\\b") == std::string::npos ||
|
||||
result.find("\\cf1") == std::string::npos || !pdf_written ||
|
||||
!pdf_data.starts_with("%PDF-") ||
|
||||
pdf_data.find("Plain") == std::string::npos ||
|
||||
pdf_data.find("Bold") == std::string::npos ||
|
||||
pdf_data.find("Second paragraph") == std::string::npos ||
|
||||
pdf_data.find("/Helvetica-Bold") == std::string::npos ||
|
||||
pdf_data.find("xref") == std::string::npos ||
|
||||
pdf_data.find("%%EOF") == std::string::npos) {
|
||||
std::cerr << "DOCX round trip failed: write=" << written
|
||||
<< " read=" << read << " pdf=" << pdf_written
|
||||
<< " bytes=" << result.size() << '\n';
|
||||
return 2;
|
||||
}
|
||||
std::cout << "DOCX round trip passed (" << result.size() << " RTF bytes)\n";
|
||||
return 0;
|
||||
}
|
||||
@@ -1582,6 +1582,72 @@ void draw_neighbor_sheet(HDC dc, const RECT& sheet, const RECT& client,
|
||||
DeleteObject(border_brush);
|
||||
}
|
||||
|
||||
void draw_continuous_page_workspace(HWND pane, HDC dc) {
|
||||
int page_left = 0;
|
||||
int page_top = 0;
|
||||
int page_right = 0;
|
||||
int page_bottom = 0;
|
||||
int page_gap = 0;
|
||||
int has_previous = 0;
|
||||
int has_next = 0;
|
||||
if (!OpusGetWin95ContinuousPageMetrics(
|
||||
pane, &page_left, &page_top, &page_right, &page_bottom,
|
||||
&page_gap, &has_previous, &has_next)) {
|
||||
return;
|
||||
}
|
||||
|
||||
RECT client{};
|
||||
GetClientRect(pane, &client);
|
||||
const int page_height = page_bottom - page_top;
|
||||
if (page_right <= page_left || page_height <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* The original Page View eraser assumes that the page consumes the
|
||||
window width. In the continuous-page shell that can leave white
|
||||
invalidation bands across the gray inter-page gap. Restore the
|
||||
workspace after the legacy paint, excluding only real sheets, before
|
||||
drawing cached neighbor pages. */
|
||||
const int saved = SaveDC(dc);
|
||||
IntersectClipRect(dc, client.left, client.top,
|
||||
client.right, client.bottom);
|
||||
const int page_step = page_height + page_gap;
|
||||
const int offsets[] = {
|
||||
0,
|
||||
has_previous ? -page_step : 0,
|
||||
has_next ? page_step : 0};
|
||||
const int first = has_previous ? 0 : 1;
|
||||
const int last = has_next ? 3 : 2;
|
||||
for (int index = first; index < last; ++index) {
|
||||
const RECT sheet{
|
||||
page_left, page_top + offsets[index],
|
||||
page_right, page_bottom + offsets[index]};
|
||||
ExcludeClipRect(dc, sheet.left, sheet.top,
|
||||
sheet.right, sheet.bottom);
|
||||
}
|
||||
FillRect(dc, &client, GetSysColorBrush(COLOR_APPWORKSPACE));
|
||||
RestoreDC(dc, saved);
|
||||
|
||||
/* Rebuild the current sheet's shadow after cleaning the surrounding
|
||||
workspace. Clip the shadow away from the page so it cannot cover
|
||||
document pixels that the legacy formatter just drew. */
|
||||
const RECT current{page_left, page_top, page_right, page_bottom};
|
||||
RECT shadow = current;
|
||||
OffsetRect(&shadow, 3, 3);
|
||||
const int shadow_saved = SaveDC(dc);
|
||||
IntersectClipRect(dc, shadow.left, shadow.top,
|
||||
shadow.right, shadow.bottom);
|
||||
ExcludeClipRect(dc, current.left, current.top,
|
||||
current.right, current.bottom);
|
||||
HBRUSH shadow_brush = CreateSolidBrush(RGB(96, 96, 96));
|
||||
FillRect(dc, &shadow, shadow_brush);
|
||||
DeleteObject(shadow_brush);
|
||||
RestoreDC(dc, shadow_saved);
|
||||
HBRUSH border_brush = CreateSolidBrush(RGB(96, 96, 96));
|
||||
FrameRect(dc, ¤t, border_brush);
|
||||
DeleteObject(border_brush);
|
||||
}
|
||||
|
||||
void draw_continuous_page_neighbors(HWND pane, HDC dc) {
|
||||
int page_left = 0;
|
||||
int page_top = 0;
|
||||
@@ -2404,6 +2470,7 @@ LRESULT CALLBACK document_pane_proc(HWND pane, UINT message,
|
||||
HDC dc = GetDC(pane);
|
||||
if (dc != nullptr) {
|
||||
update_current_page_snapshot(pane, dc);
|
||||
draw_continuous_page_workspace(pane, dc);
|
||||
draw_continuous_page_neighbors(pane, dc);
|
||||
draw_vertical_ruler(pane, dc);
|
||||
ReleaseDC(pane, dc);
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <cwchar>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <iterator>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
@@ -569,6 +571,62 @@ bool window_is_responsive(const HANDLE process, const HWND window) {
|
||||
2000, &message_result) != 0;
|
||||
}
|
||||
|
||||
bool capture_window_bmp(const HWND window, const wchar_t* path) {
|
||||
RECT bounds{};
|
||||
if (window == nullptr || path == nullptr || !GetWindowRect(window, &bounds))
|
||||
return false;
|
||||
const int width = bounds.right - bounds.left;
|
||||
const int height = bounds.bottom - bounds.top;
|
||||
if (width <= 0 || height <= 0) return false;
|
||||
HDC screen = GetDC(nullptr);
|
||||
HDC memory = screen != nullptr ? CreateCompatibleDC(screen) : nullptr;
|
||||
HBITMAP bitmap = memory != nullptr ?
|
||||
CreateCompatibleBitmap(screen, width, height) : nullptr;
|
||||
HGDIOBJ previous = bitmap != nullptr ? SelectObject(memory, bitmap) : nullptr;
|
||||
const bool copied = bitmap != nullptr &&
|
||||
BitBlt(memory, 0, 0, width, height, screen, bounds.left, bounds.top,
|
||||
SRCCOPY | CAPTUREBLT) != FALSE;
|
||||
BITMAPINFO info{};
|
||||
info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
||||
info.bmiHeader.biWidth = width;
|
||||
info.bmiHeader.biHeight = -height;
|
||||
info.bmiHeader.biPlanes = 1;
|
||||
info.bmiHeader.biBitCount = 32;
|
||||
info.bmiHeader.biCompression = BI_RGB;
|
||||
std::vector<unsigned char> pixels(
|
||||
static_cast<std::size_t>(width) * height * 4);
|
||||
const bool read = copied && GetDIBits(
|
||||
memory, bitmap, 0, height, pixels.data(), &info, DIB_RGB_COLORS) != 0;
|
||||
bool written = false;
|
||||
if (read) {
|
||||
BITMAPFILEHEADER file_header{};
|
||||
file_header.bfType = 0x4d42;
|
||||
file_header.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
|
||||
file_header.bfSize = file_header.bfOffBits +
|
||||
static_cast<DWORD>(pixels.size());
|
||||
HANDLE file = CreateFileW(path, GENERIC_WRITE, 0, nullptr,
|
||||
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
if (file != INVALID_HANDLE_VALUE) {
|
||||
DWORD count = 0;
|
||||
written = WriteFile(file, &file_header, sizeof(file_header),
|
||||
&count, nullptr) &&
|
||||
count == sizeof(file_header) &&
|
||||
WriteFile(file, &info.bmiHeader,
|
||||
sizeof(info.bmiHeader), &count, nullptr) &&
|
||||
count == sizeof(info.bmiHeader) &&
|
||||
WriteFile(file, pixels.data(),
|
||||
static_cast<DWORD>(pixels.size()), &count,
|
||||
nullptr) && count == pixels.size();
|
||||
CloseHandle(file);
|
||||
}
|
||||
}
|
||||
if (previous != nullptr) SelectObject(memory, previous);
|
||||
if (bitmap != nullptr) DeleteObject(bitmap);
|
||||
if (memory != nullptr) DeleteDC(memory);
|
||||
if (screen != nullptr) ReleaseDC(nullptr, screen);
|
||||
return written;
|
||||
}
|
||||
|
||||
int fail(PROCESS_INFORMATION& process, const int code,
|
||||
const char* message) {
|
||||
std::cerr << message << '\n';
|
||||
@@ -596,7 +654,8 @@ int wmain(const int argument_count, wchar_t** arguments) {
|
||||
"usage: opus_word1_ui_test WORD1.exe "
|
||||
"[--typing|--interaction|--selection|--caret|--formatting|--color|"
|
||||
"--font-typing|--clipboard|--about|--save-as|--pdf-export|"
|
||||
"--docx-open FILE]\n";
|
||||
"--docx-open FILE|--docx-format-open FILE|"
|
||||
"--docx-pdf-export FILE]\n";
|
||||
return 1;
|
||||
}
|
||||
const bool typing_mode =
|
||||
@@ -631,9 +690,17 @@ int wmain(const int argument_count, wchar_t** arguments) {
|
||||
const bool pdf_export_mode =
|
||||
argument_count == 3 &&
|
||||
std::wcscmp(arguments[2], L"--pdf-export") == 0;
|
||||
const bool docx_pdf_export_mode =
|
||||
argument_count == 4 &&
|
||||
std::wcscmp(arguments[2], L"--docx-pdf-export") == 0;
|
||||
const bool formatted_docx_open_mode =
|
||||
argument_count == 4 &&
|
||||
(std::wcscmp(arguments[2], L"--docx-format-open") == 0 ||
|
||||
docx_pdf_export_mode);
|
||||
const bool docx_open_mode =
|
||||
argument_count == 4 &&
|
||||
std::wcscmp(arguments[2], L"--docx-open") == 0;
|
||||
(std::wcscmp(arguments[2], L"--docx-open") == 0 ||
|
||||
formatted_docx_open_mode);
|
||||
if (argument_count == 3 && !typing_mode && !interaction_mode &&
|
||||
!selection_mode && !caret_mode && !formatting_mode && !color_mode &&
|
||||
!font_typing_mode && !clipboard_mode && !about_mode &&
|
||||
@@ -648,6 +715,7 @@ int wmain(const int argument_count, wchar_t** arguments) {
|
||||
|
||||
std::string docx_path;
|
||||
std::string pdf_path;
|
||||
bool keep_pdf = false;
|
||||
if (docx_open_mode) {
|
||||
const int path_size = WideCharToMultiByte(
|
||||
CP_ACP, 0, arguments[3], -1, nullptr, 0, nullptr, nullptr);
|
||||
@@ -668,7 +736,18 @@ int wmain(const int argument_count, wchar_t** arguments) {
|
||||
SetEnvironmentVariableA("WORD1_TEST_FILE_DIALOG_PATH",
|
||||
docx_path.c_str());
|
||||
}
|
||||
if (pdf_export_mode) {
|
||||
if (pdf_export_mode || docx_pdf_export_mode) {
|
||||
char requested_pdf[32768]{};
|
||||
const DWORD requested_length = GetEnvironmentVariableA(
|
||||
"WORD1_TEST_KEEP_PDF", requested_pdf,
|
||||
static_cast<DWORD>(std::size(requested_pdf)));
|
||||
keep_pdf = requested_length > 0 &&
|
||||
requested_length < std::size(requested_pdf);
|
||||
if (keep_pdf) {
|
||||
pdf_path = requested_pdf;
|
||||
DeleteFileA(pdf_path.c_str());
|
||||
SetEnvironmentVariableA("WORD1_TEST_PDF_PATH", pdf_path.c_str());
|
||||
} else {
|
||||
char temporary_directory[MAX_PATH]{};
|
||||
char temporary_seed[MAX_PATH]{};
|
||||
if (GetTempPathA(static_cast<DWORD>(std::size(temporary_directory)),
|
||||
@@ -681,6 +760,7 @@ int wmain(const int argument_count, wchar_t** arguments) {
|
||||
DeleteFileA(temporary_seed);
|
||||
pdf_path = std::string(temporary_seed) + ".pdf";
|
||||
SetEnvironmentVariableA("WORD1_TEST_PDF_PATH", pdf_path.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
std::wstring command_line = L"\"" + std::wstring(arguments[1]) + L"\"";
|
||||
@@ -733,7 +813,7 @@ int wmain(const int argument_count, wchar_t** arguments) {
|
||||
<< (pane != nullptr ? SendMessageW(
|
||||
pane, kWmOpusX64QuerySelection, 41, 0) : 0)
|
||||
<< '\n';
|
||||
DeleteFileA(pdf_path.c_str());
|
||||
if (!keep_pdf) DeleteFileA(pdf_path.c_str());
|
||||
return fail(process, 85,
|
||||
"PDF export test could not prepare its document");
|
||||
}
|
||||
@@ -761,7 +841,7 @@ int wmain(const int argument_count, wchar_t** arguments) {
|
||||
WaitForSingleObject(process.hProcess, 2000);
|
||||
CloseHandle(process.hThread);
|
||||
CloseHandle(process.hProcess);
|
||||
DeleteFileA(pdf_path.c_str());
|
||||
if (!keep_pdf) DeleteFileA(pdf_path.c_str());
|
||||
if (!pdf_written) {
|
||||
std::cerr << "PDF export failed or crashed: exit=0x" << std::hex
|
||||
<< exit_code << std::dec << '\n';
|
||||
@@ -845,16 +925,87 @@ int wmain(const int argument_count, wchar_t** arguments) {
|
||||
SendMessageW(pane, kWmOpusX64QuerySelection, 41, 0) : 0;
|
||||
const LRESULT displayed_lines = pane != nullptr ?
|
||||
SendMessageW(pane, kWmOpusX64QuerySelection, 30, 0) : 0;
|
||||
const LRESULT title_hps = pane != nullptr ?
|
||||
SendMessageW(pane, kWmOpusX64QuerySelection, 52, 0) : -1;
|
||||
const LRESULT title_bold = pane != nullptr ?
|
||||
SendMessageW(pane, kWmOpusX64QuerySelection, 87, 0) : -1;
|
||||
const LRESULT title_color = pane != nullptr ?
|
||||
SendMessageW(pane, kWmOpusX64QuerySelection, 90, 0) : -1;
|
||||
const LRESULT title_alignment = pane != nullptr ?
|
||||
SendMessageW(pane, kWmOpusX64QuerySelection, 91, 0) : -1;
|
||||
const LRESULT page_width = pane != nullptr ?
|
||||
SendMessageW(pane, kWmOpusX64QuerySelection, 97, 0) : -1;
|
||||
const LRESULT page_height = pane != nullptr ?
|
||||
SendMessageW(pane, kWmOpusX64QuerySelection, 98, 0) : -1;
|
||||
const LRESULT left_margin = pane != nullptr ?
|
||||
SendMessageW(pane, kWmOpusX64QuerySelection, 99, 0) : -1;
|
||||
const LRESULT top_margin = pane != nullptr ?
|
||||
SendMessageW(pane, kWmOpusX64QuerySelection, 101, 0) : -1;
|
||||
const LRESULT body_font_index = pane != nullptr ?
|
||||
SendMessageW(pane, kWmOpusX64QuerySelection, 103, 20) : -1;
|
||||
const LRESULT body_font_charset = pane != nullptr ?
|
||||
SendMessageW(pane, kWmOpusX64QuerySelection, 104, 20) : -1;
|
||||
wchar_t capture_path[32768]{};
|
||||
const DWORD capture_length = GetEnvironmentVariableW(
|
||||
L"WORD1_TEST_CAPTURE_PATH", capture_path,
|
||||
static_cast<DWORD>(std::size(capture_path)));
|
||||
if (capture_length > 0 && capture_length < std::size(capture_path)) {
|
||||
ShowWindow(main_window, SW_RESTORE);
|
||||
SetForegroundWindow(main_window);
|
||||
UpdateWindow(main_window);
|
||||
Sleep(2000);
|
||||
if (!capture_window_bmp(main_window, capture_path)) {
|
||||
return fail(process, 84, "DOCX capture failed");
|
||||
}
|
||||
}
|
||||
bool rich_pdf_valid = !docx_pdf_export_mode;
|
||||
if (docx_pdf_export_mode &&
|
||||
PostMessageW(main_window, kWmCommand, kExportPdf, 0)) {
|
||||
const ULONGLONG pdf_deadline = GetTickCount64() + 15000;
|
||||
do {
|
||||
std::ifstream input(pdf_path, std::ios::binary);
|
||||
if (input) {
|
||||
const std::string pdf_data(
|
||||
std::istreambuf_iterator<char>(input), {});
|
||||
rich_pdf_valid = pdf_data.starts_with("%PDF-") &&
|
||||
pdf_data.find("JUSTIN MARSHALL") != std::string::npos &&
|
||||
pdf_data.find("/Helvetica-Bold") != std::string::npos &&
|
||||
pdf_data.find("0 0 1 rg") != std::string::npos &&
|
||||
pdf_data.find("/MediaBox [0 0 612 792]") !=
|
||||
std::string::npos;
|
||||
}
|
||||
if (!rich_pdf_valid) Sleep(100);
|
||||
} while (!rich_pdf_valid && GetTickCount64() < pdf_deadline &&
|
||||
WaitForSingleObject(process.hProcess, 0) != WAIT_OBJECT_0);
|
||||
}
|
||||
TerminateProcess(process.hProcess, 0);
|
||||
WaitForSingleObject(process.hProcess, 2000);
|
||||
CloseHandle(process.hThread);
|
||||
CloseHandle(process.hProcess);
|
||||
if (!reopened || pane == nullptr || imported_length < 100 ||
|
||||
displayed_lines < 1) {
|
||||
displayed_lines < 1 ||
|
||||
(formatted_docx_open_mode &&
|
||||
(title_hps < 40 || title_bold != 1 || title_color != 2 ||
|
||||
title_alignment != 1 || page_width != 12240 ||
|
||||
page_height != 15840 || left_margin != 835 ||
|
||||
top_margin != 691 || body_font_index != 2 ||
|
||||
body_font_charset != ANSI_CHARSET)) || !rich_pdf_valid) {
|
||||
std::cerr << "DOCX open state: length=" << imported_length
|
||||
<< " displayedLines=" << displayed_lines << '\n';
|
||||
<< " displayedLines=" << displayed_lines
|
||||
<< " title={hps:" << title_hps
|
||||
<< ",bold:" << title_bold
|
||||
<< ",color:" << title_color
|
||||
<< ",alignment:" << title_alignment << "}"
|
||||
<< " page={" << page_width << "x" << page_height
|
||||
<< ",left:" << left_margin << ",top:" << top_margin
|
||||
<< "} bodyFont={index:" << body_font_index
|
||||
<< ",charset:" << body_font_charset << "}\n";
|
||||
if (docx_pdf_export_mode)
|
||||
std::cerr << "richPdf=" << rich_pdf_valid << '\n';
|
||||
if (!keep_pdf && !pdf_path.empty()) DeleteFileA(pdf_path.c_str());
|
||||
return 84;
|
||||
}
|
||||
if (!keep_pdf && !pdf_path.empty()) DeleteFileA(pdf_path.c_str());
|
||||
return 0;
|
||||
}
|
||||
if (about_mode) {
|
||||
|
||||
Reference in New Issue
Block a user