diff --git a/printers/docx/src/index.ts b/printers/docx/src/index.ts index f87236b..5f17ece 100644 --- a/printers/docx/src/index.ts +++ b/printers/docx/src/index.ts @@ -11,6 +11,7 @@ import { ListNode, Node, RawNode } from '@md-to-latex/converter/dist/ast/node'; import * as docx from 'docx'; import { XmlComponent } from 'docx'; import { validateDocxRootNode } from './validation'; +import { getDocumentGlobalStyles } from './styles'; const processNode: DocxPrinterVisitor = function (printer, node) { @@ -79,66 +80,154 @@ export async function printerResultToBuffer( console.log(result); const doc = new docx.Document({ + styles: getDocumentGlobalStyles(), numbering: { - config: printer.wordListRefStore.map< - docx.INumberingOptions['config'][0] - >(n => ({ - // TODO: fully prepare the list styles for ordered and unordered list - reference: n.ref, - levels: [ - { - level: 0, - format: docx.LevelFormat.UPPER_ROMAN, - text: '0.%1', - alignment: docx.AlignmentType.START, - style: { - paragraph: { - leftTabStop: docx.convertMillimetersToTwip(40), - indent: { - firstLine: - docx.convertMillimetersToTwip(20), + config: [ + ...printer.wordListRefStore.map< + docx.INumberingOptions['config'][0] + >(n => ({ + // TODO: fully prepare the list styles for ordered and unordered list + reference: n.ref, + levels: [ + { + level: 0, + format: docx.LevelFormat.RUSSIAN_UPPER, + text: '%1)', + alignment: docx.AlignmentType.START, + style: { + paragraph: { + leftTabStop: docx.convertMillimetersToTwip( + 15 + 10, + ), + indent: { + firstLine: + docx.convertMillimetersToTwip(15), + }, }, }, }, - }, - { - level: 1, - format: docx.LevelFormat.UPPER_ROMAN, - text: '1.%1', - alignment: docx.AlignmentType.START, - style: { - paragraph: { - leftTabStop: docx.convertMillimetersToTwip(60), - indent: { - firstLine: - docx.convertMillimetersToTwip(40), + { + level: 1, + format: docx.LevelFormat.DECIMAL, + text: '%2)', + alignment: docx.AlignmentType.START, + style: { + paragraph: { + leftTabStop: docx.convertMillimetersToTwip( + 15 + 15 + 10, + ), + indent: { + firstLine: + docx.convertMillimetersToTwip( + 15 + 15, + ), + }, }, }, }, - }, - { - level: 2, - format: docx.LevelFormat.UPPER_ROMAN, - text: '%2.%1', - alignment: docx.AlignmentType.START, - style: { - paragraph: { - leftTabStop: docx.convertMillimetersToTwip(80), - indent: { - firstLine: - docx.convertMillimetersToTwip(60), + { + level: 2, + format: docx.LevelFormat.UPPER_ROMAN, + text: '%3)', + alignment: docx.AlignmentType.START, + style: { + paragraph: { + leftTabStop: docx.convertMillimetersToTwip( + 15 + 15 + 15 + 10, + ), + indent: { + firstLine: + docx.convertMillimetersToTwip( + 15 + 15 + 15, + ), + }, }, }, }, - }, - ], - })), + ], + })), + { + reference: 'heading-ref', + levels: [ + { + level: 0, + format: docx.LevelFormat.DECIMAL, + text: '%1', + alignment: docx.AlignmentType.START, + style: { + paragraph: { + leftTabStop: + docx.convertMillimetersToTwip(10), + }, + }, + }, + { + level: 1, + format: docx.LevelFormat.DECIMAL, + text: '%1.%2', + alignment: docx.AlignmentType.START, + style: { + paragraph: { + leftTabStop: docx.convertMillimetersToTwip( + 20 + 15, + ), + }, + }, + }, + { + level: 2, + format: docx.LevelFormat.DECIMAL, + text: '%1.%2.%3', + alignment: docx.AlignmentType.START, + style: { + paragraph: { + leftTabStop: docx.convertMillimetersToTwip( + 20 + 15, + ), + }, + }, + }, + ], + }, + ], }, features: { updateFields: true, }, sections: [ { + properties: { + page: { + size: { + orientation: docx.PageOrientation.PORTRAIT, + height: docx.convertMillimetersToTwip(297), + width: docx.convertMillimetersToTwip(210), + }, + margin: { + top: docx.convertMillimetersToTwip(20), + right: docx.convertMillimetersToTwip(10), + bottom: docx.convertMillimetersToTwip(20), + left: docx.convertMillimetersToTwip(30), + }, + }, + }, + footers: { + default: new docx.Footer({ + children: [ + new docx.Paragraph({ + indent: { + firstLine: 0, + }, + alignment: docx.AlignmentType.CENTER, + children: [ + new docx.TextRun({ + children: [docx.PageNumber.CURRENT], + }), + ], + }), + ], + }), + }, children: [ ...result.map(n => { if ( diff --git a/printers/docx/src/printer.ts b/printers/docx/src/printer.ts index 28e4048..497247a 100644 --- a/printers/docx/src/printer.ts +++ b/printers/docx/src/printer.ts @@ -152,6 +152,7 @@ export async function printFormulaProcessedNode( await formulaNodeToPicture(node.text), ], alignment: AlignmentType.CENTER, + style: 'formula-table-cell', }), ], verticalAlign: VerticalAlign.CENTER, @@ -161,6 +162,7 @@ export async function printFormulaProcessedNode( new docx.Paragraph({ text: `(${node.index + 1})`, alignment: AlignmentType.RIGHT, + style: 'formula-table-cell-number', }), ], verticalAlign: VerticalAlign.CENTER, @@ -239,6 +241,7 @@ export function getWordTable(info: WordTableInfo): PrinterFunctionResult { }), ...info.tableTitle, ], + style: 'table-caption', }), new docx.Table({ width: { @@ -246,6 +249,7 @@ export function getWordTable(info: WordTableInfo): PrinterFunctionResult { size: 100, }, rows: [info.header, ...info.content], + style: 'table', }), ], diagnostic: [], diff --git a/printers/docx/src/styles.ts b/printers/docx/src/styles.ts new file mode 100644 index 0000000..1703c05 --- /dev/null +++ b/printers/docx/src/styles.ts @@ -0,0 +1,183 @@ +import * as docx from 'docx'; +import { AlignmentType } from 'docx'; + +function fontSizeToDocxFontSize(size: number): number { + return size * 2; +} + +export function getDocumentGlobalStyles(): docx.IStylesOptions { + const defaultSpacing = { + after: 0, + before: 0, + // I don't know, it's 1.5 spacing + line: 360, + }; + const defaultIndent = { + firstLine: '1.25cm', + }; + const defaultFont = { + font: 'Tinos', + size: fontSizeToDocxFontSize(14), + }; + const defaultPictureSpacingMm = 6; + const defaultTableSpacingMm = defaultPictureSpacingMm * 1.5; + + return { + default: { + document: { + paragraph: { + alignment: AlignmentType.JUSTIFIED, + indent: { ...defaultIndent }, + spacing: { ...defaultSpacing }, + }, + run: { ...defaultFont }, + }, + heading1: { + paragraph: { + numbering: { + reference: 'heading-ref', + level: 0, + }, + alignment: AlignmentType.CENTER, + indent: { firstLine: 0 }, + spacing: { ...defaultSpacing }, + }, + run: { ...defaultFont, bold: true }, + }, + heading2: { + paragraph: { + numbering: { + reference: 'heading-ref', + level: 1, + }, + alignment: AlignmentType.JUSTIFIED, + indent: { ...defaultIndent }, + spacing: { ...defaultSpacing }, + }, + run: { ...defaultFont, bold: true }, + }, + heading3: { + paragraph: { + numbering: { + reference: 'heading-ref', + level: 2, + }, + alignment: AlignmentType.JUSTIFIED, + indent: { ...defaultIndent }, + spacing: { ...defaultSpacing }, + }, + run: { ...defaultFont, bold: true }, + }, + }, + paragraphStyles: [ + { + id: 'table-caption', + name: 'table-caption', + run: {}, + paragraph: { + spacing: { + after: docx.convertMillimetersToTwip( + defaultPictureSpacingMm, + ), + }, + indent: { + firstLine: 0, + }, + }, + }, + { + id: 'table', + name: 'table', + paragraph: { + spacing: { + after: docx.convertMillimetersToTwip( + defaultTableSpacingMm, + ), + }, + indent: { + firstLine: 0, + }, + }, + }, + { + id: 'table-cell', + name: 'table-cell', + paragraph: { + indent: { + firstLine: 0, + }, + }, + }, + { + id: 'picture-caption', + name: 'picture-caption', + run: {}, + paragraph: { + alignment: AlignmentType.CENTER, + spacing: { + after: docx.convertMillimetersToTwip( + defaultPictureSpacingMm, + ), + }, + indent: { + firstLine: 0, + }, + }, + }, + { + id: 'picture', + name: 'picture', + paragraph: { + keepNext: true, + alignment: AlignmentType.CENTER, + spacing: { + before: docx.convertMillimetersToTwip( + defaultPictureSpacingMm, + ), + }, + indent: { + firstLine: 0, + }, + }, + }, + { + id: 'formula-picture', + name: 'formula-picture', + paragraph: { + alignment: AlignmentType.CENTER, + spacing: { + before: docx.convertMillimetersToTwip( + defaultPictureSpacingMm, + ), + after: docx.convertMillimetersToTwip( + defaultPictureSpacingMm, + ), + }, + indent: { + firstLine: 0, + }, + }, + }, + { + id: 'formula-table-cell', + name: 'formula-table-cell', + paragraph: { + alignment: AlignmentType.CENTER, + indent: { + firstLine: 0, + }, + }, + }, + { + id: 'formula-table-cell-number', + name: 'formula-table-cell-number', + paragraph: { + alignment: AlignmentType.CENTER, + indent: { + firstLine: 0, + }, + }, + }, + ], + }; +} diff --git a/printers/docx/src/visitors.ts b/printers/docx/src/visitors.ts index cba2bfa..5db3dd1 100644 --- a/printers/docx/src/visitors.ts +++ b/printers/docx/src/visitors.ts @@ -316,6 +316,7 @@ export const processingVisitors: ProcessingVisitors = { new Paragraph({ children: [...resultPicture.result], keepNext: true, + style: 'picture', }), ...resultCaption.result, ], @@ -458,6 +459,7 @@ export const processingVisitors: ProcessingVisitors = { children: [ new Paragraph({ children: result.result, + style: 'table-cell', }), ], }), @@ -544,7 +546,7 @@ export const processingVisitors: ProcessingVisitors = { result: [ new docx.Paragraph({ children: [await formulaNodeToPicture(node.text)], - alignment: AlignmentType.CENTER, + style: 'formula-picture', }), ], diagnostic: [],